Rendern auf dem Server
Alle Pakete von reelkit laufen auf dem Server. Du kannst sie mit Next.js, Remix, Angular SSR, Nuxt 3 oder jedem anderen SSR-Setup importieren und rendern.
So funktioniert es
Der Slider-Controller im Core ist reine Logik und greift beim Erstellen nicht auf das DOM zu. Gesten-Listener, Tastatur-Events und Animationen werden erst in den Lifecycle-Hooks auf dem Client angehängt.
Beim SSR rendert Reel für React einen statischen Container mit den anfangs sichtbaren Slides (meist 3: vorheriger, aktueller, nächster), wenn es ein size hat, und einen leeren Container, wenn es sich selbst misst. Bei der Hydration hängt es die Controller für Gesten, Tastatur und Mausrad an und macht damit alles interaktiv.
Reel für Vue rendert auf dem Server einen leeren Container und zeichnet seine Slides erst nach der Hydration. Mit einer size passt dieser Container nicht zu dem, was der Client zuerst rendert. In Nuxt gehört es dann in <ClientOnly> (siehe Nuxt 3).
| Paket | SSR-sicher | Hinweise |
|---|---|---|
@reelkit/core | Ja | Reine Logik, keine Browser-APIs beim Import oder Erstellen |
@reelkit/react | Ja | Reel und ReelIndicator rendern auf dem Server gültiges HTML |
@reelkit/angular | Ja | Standalone-Komponenten, SSR-kompatibel mit Angular SSR (@angular/ssr) |
@reelkit/vue | Ja | Lässt sich auf dem Server importieren und rendern; mit einer size gehört Reel in Nuxt 3 in <ClientOnly> |
@reelkit/react-reel-player | Ja | Rendert im geschlossenen Zustand nichts (isOpen=false) |
@reelkit/react-lightbox | Ja | Rendert im geschlossenen Zustand nichts (isOpen=false) |
@reelkit/angular-reel-player | Ja | Rendert im geschlossenen Zustand nichts (isOpen=false) |
@reelkit/angular-lightbox | Ja | Rendert im geschlossenen Zustand nichts (isOpen=false) |
@reelkit/vue-reel-player | Ja | Rendert im geschlossenen Zustand nichts (isOpen=false) |
@reelkit/vue-lightbox | Ja | Rendert im geschlossenen Zustand nichts (isOpen=false) |
@reelkit/stories-core | Ja | Unabhängig vom Framework, kein Zugriff auf das DOM |
@reelkit/react-stories-player | Ja | Rendert im geschlossenen Zustand nichts (isOpen=false) |
@reelkit/angular-stories-player | Ja | Rendert im geschlossenen Zustand nichts (isOpen=false) |
@reelkit/vue-stories-player | Ja | Rendert im geschlossenen Zustand nichts (isOpen=false) |
Next.js App Router
Reel nutzt Browser-Events und Refs und läuft deshalb als Client Component. Setze die Direktive "use client" an den Anfang der Datei, die Reel verwendet:
'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>
);
}Die Daten kannst du in einer Server Component laden und nach unten weitergeben:
// 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
Der Pages Router funktioniert ohne zusätzliche Konfiguration. Die Komponente wird beim SSR gerendert und auf dem Client hydriert:
// 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]} />}
/>
);
}Responsive Größe mit SSR
Lass das Prop size ganz weg. Ohne size misst Reel seinen Container auf dem Client selbst über ResizeObserver. Beim SSR rendert der Slider einen leeren Container; bei der Hydration misst er und rendert die Slides sofort:
'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]} />}
/>
);
}So funktioniert die automatische Größe
Ohne size muss die Größe des Containers per CSS festgelegt sein (Flex- oder Grid-Elternelement, feste Breite und Höhe oder Prozentwerte). Der Slider rendert nichts, bis die erste Messung fertig ist, füllt danach die gemessene Fläche und reagiert auch auf spätere Größenänderungen automatisch.
Feste Größe (manueller Weg)
Für Kontrolle auf den Pixel genau übergibst du ein festes Prop size. Da window.innerWidth beim SSR nicht verfügbar ist, gib einen Standardwert vor und aktualisiere ihn beim Mounten:
'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]} />}
/>
);
}Tipp
Wähle als Standard die Größe, die zu deinem häufigsten Viewport passt (zum Beispiel Mobile First). Weicht der tatsächliche Viewport ab, passt sich der Slider bei der Hydration sofort an.
Overlay-Komponenten
ReelPlayerOverlay, LightboxOverlay und StoriesOverlay rendern bei isOpen={false} nichts und sind damit von Haus aus SSR-sicher. Ihr Portal mounten sie erst beim Öffnen (meist nach einer Interaktion auf dem Client):
'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
Alle Angular-Komponenten sind mit @angular/ssr SSR-sicher. Der Slider-Controller entsteht in ngOnInit, und die Arbeit, die einen Browser braucht, etwa das Messen des Containers, wird auf dem Server über isPlatformBrowser übersprungen. Overlay-Komponenten rendern bei isOpen=false nichts und erzeugen beim Server-Rendering daher kein Markup.
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
Die Vue-Komponenten von ReelKit lassen sich ohne Fehler auf dem Server importieren und rendern. Reel schickt vom Server einen leeren Container und zeichnet seine Slides erst nach der Hydration. Wenn sich Reel selbst misst, wird dieser Container ab @reelkit/vue 0.9.1 ohne Abweichung hydriert; in früheren Versionen hatte er keine Höhe, sodass keine Slides erschienen. Mit einer festen size enthält schon der erste Render auf dem Client Slides, und das ergibt eine Abweichung bei der Hydration. In diesem Fall oder mit einer früheren Version umschließe Reel mit einer Komponente <ClientOnly> oder verwende die Endung .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>Die Komponente Feed verwendet Reel ganz normal:
<!-- 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>Responsive Größe mit SSR
Lass das Prop size weg, um die automatische Messung zu nutzen. Reel nimmt dann 100 % seines Elternelements ein und braucht ab @reelkit/vue 0.9.1 kein <ClientOnly>: Der Server schickt einen leeren Container in voller Größe, und der Client misst ihn und rendert die Slides. Das Beispiel behält die Hülle, weil sie auch mit früheren Versionen funktioniert:
<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>Overlay-Komponenten
ReelPlayerOverlay, LightboxOverlay und StoriesOverlay rendern nichts, solange isOpen false ist. Sie brauchen daher kein <ClientOnly> und erzeugen beim Server-Rendering kein Markup. Ihren Inhalt teleportieren sie erst in den Body, wenn sie auf dem Client geöffnet werden.
Den Core direkt nutzen
Wenn du @reelkit/core direkt für eine eigene Framework-Integration nutzt, kannst du den Controller auf dem Server erstellen. attach() und observe() rufst du auf dem Client auf:
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();
}Zusammenfassung
Was ohne weitere Einrichtung funktioniert
- Jedes Paket von reelkit auf dem Server importieren
- Slider-Komponenten beim SSR rendern (ergibt gültiges statisches HTML)
- Controller auf dem Server erstellen
- Overlay-Komponenten bei
isOpen=false
Worauf du achten solltest
- Lass
sizefür die automatische Messung weg oder gib einen Standardwert vor, wenn die Maße vom Viewport abhängen - Rendere
Reelfür Vue in Nuxt innerhalb von<ClientOnly>, wenn es einesizehat oder du@reelkit/vuevor 0.9.1 nutzt - Ruf
attach()/observe()nicht auf dem Server auf, wenn du den Core direkt nutzt