Рендеринг на сервері
Усі пакети reelkit працюють на сервері. Імпортуйте й рендерте їх у Next.js, Remix, Angular SSR, Nuxt 3 чи будь-якій іншій конфігурації із SSR.
Як це працює
Контролер слайдера в ядрі — чиста логіка, яка під час створення не звертається до DOM. Обробники жестів, події клавіатури та анімації підключаються лише в клієнтських етапах життєвого циклу.
Під час SSR компонент Reel для React рендерить статичний контейнер із початково видимими слайдами (зазвичай 3: попередній, поточний, наступний), якщ о має size, і порожній контейнер, якщо вимірює себе сам. Під час гідратації він підключає контролери жестів, клавіатури та колеса миші, і все стає інтерактивним.
Reel для Vue рендерить на сервері порожній контейнер і малює слайди після гідратації. Коли задано size, цей контейнер не збігається з першим рендером на клієнті, тож у Nuxt його кладуть у <ClientOnly> (див. Nuxt 3).
| Пакет | Безпечний для SSR | Примітки |
|---|---|---|
@reelkit/core | Так | Чиста логіка, жодних браузерних API під час імпорту чи створення |
@reelkit/react | Так | Reel і ReelIndicator рендерять коректний HTML на сервері |
@reelkit/angular | Так | Автономні компоненти, сумісні з SSR в Angular SSR (@angular/ssr) |
@reelkit/vue | Так | Безпечно імпортувати й рендерити на сервері; якщо задано size, у Nuxt 3 кладіть Reel у <ClientOnly> |
@reelkit/react-reel-player | Так | Нічого не рендерить, поки закритий (isOpen=false) |
@reelkit/react-lightbox | Так | Нічого не рендерить, поки закритий (isOpen=false) |
@reelkit/angular-reel-player | Так | Нічого не рендерить, поки закритий (isOpen=false) |
@reelkit/angular-lightbox | Так | Нічого не рендерить, поки закритий (isOpen=false) |
@reelkit/vue-reel-player | Так | Нічого не рендерить, поки закритий (isOpen=false) |
@reelkit/vue-lightbox | Так | Нічого не рендерить, поки закритий (isOpen=false) |
@reelkit/stories-core | Так | Без прив’язки до фреймворку, не звертається до DOM |
@reelkit/react-stories-player | Так | Нічого не рендерить, поки закритий (isOpen=false) |
@reelkit/angular-stories-player | Так | Нічого не рендерить, поки закритий (isOpen=false) |
@reelkit/vue-stories-player | Так | Нічого не рендерить, поки закритий (isOpen=false) |
Next.js App Router
Reel використовує браузерні події та рефи, тож працює як Client Component. Додайте директиву "use client" на початку файлу, який використовує 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>
);
}Дані можна завантажити в Server Component і передати вниз:
// 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 працює без додаткових налаштувань. Компонент рендериться під час SSR і гідратується на клієнті:
// 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]} />}
/>
);
}Реактивний розмір із SSR
Не передавайте пропс size взагалі. Якщо size не задано, Reel сам вимірює свій контейнер через ResizeObserver на клієнті. Під час SSR слайдер рендерить порожній контейнер; на гідратації він вимірює його й одразу рендерить слайди:
'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]} />}
/>
);
}How auto-size works
Якщо size не задано, розмір контейнера має задавати CSS (батьківський flex або grid, явні width і height чи відсо тки). Слайдер нічого не рендерить до першого вимірювання, а потім заповнює виміряні розміри й далі сам реагує на зміни.
Явний розмір (ручний спосіб)
Для контролю до пікселя передайте явний size. Оскільки window.innerWidth недоступний під час SSR, задайте значення за замовчуванням і оновіть його після монтування:
'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]} />}
/>
);
}Tip
Оберіть розмір за замовчуванням під найпоширенішу область перегляду (наприклад, спершу мобільну). Якщо реальна область перегляду інша, слайдер миттєво підлаштується на гідратації.
Оверлейні компоненти
ReelPlayerOverlay, LightboxOverlay та StoriesOverlay нічого не рендерять, поки isOpen={false}, тож за замовчуванням безпечні для SSR. Свій портал вони монтують лише коли відкриваються — зазвичай після дії користувача на клієнті:
'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
Усі компоненти Angular безпечні для SSR з @angular/ssr. Контролер слайдера створюється в ngOnInit, а робота, якій потрібен браузер, як-от вимірювання контейнера, на сервері пропускається через isPlatformBrowser. Оверлейні компоненти нічого не рендерять, поки isOpen=false, тож під час рендерингу на сервері не дають жодної розмітки.
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
Компоненти ReelKit для Vue імпортуються й рендеряться на сервері без помилок. Reel надсилає із сервера порожній контейнер і малює слайди після гідратації. Коли Reel вимірює себе сам, цей контейнер гідратується без розбіжностей починаючи з @reelkit/vue 0.9.1; у попередніх версіях він лишався без висоти, тож слайди не з’являлися. Із явним size перший рендер на клієнті вже містить слайди, і це дає розбіжність під час гідратації. У такому разі або на попередній версії загорніть Reel у <ClientOnly> або дайте файлу суфікс .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>Компонент Feed використовує Reel як завжди:
<!-- 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>Реактивний розмір із SSR
Не передавайте пропс size, щоб увімкнути автоматичне вимірювання. Reel займає 100% розміру батька і, починаючи з @reelkit/vue 0.9.1, не потребує <ClientOnly>: сервер надсилає порожній контейнер на повний розмір, а клієнт вимірює його й рендерить слайди. Приклад зберігає обгортку, бо вона працює й на попередніх версіях:
<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>Оверлейні компоненти
ReelPlayerOverlay, LightboxOverlay та StoriesOverlay нічого не рендерять, поки isOpen хибне, тож їм не потрібен <ClientOnly> і під час рендерингу на сервері вони не дають жодної розмітки. Свій вміст вони телепортують у body лише після відкриття на клієнті.
Пряме використання ядра
Якщо ви використовуєте @reelkit/core напряму для власної інтеграції з фреймворком, контролер можна створити на сервері. Викликайте attach() та observe() на клієнті:
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();
}Підсумок
What works out of the box
- Імпорт будь-якого пакета reelkit на сервері
- Рендеринг компонентів слайдера під час SSR (дає коректний статичний HTML)
- Створення контролерів на сервері
- Оверлейні компоненти, поки
isOpen=false
What to keep in mind
- Не передавайте
sizeдля автоматичного вимірювання або задайте значення за замовчуванням, якщо розміри залежать від області перегляду - У Nuxt рендерте
Reelдля Vue всередині<ClientOnly>, коли заданоsize, або на@reelkit/vueдо 0.9.1 - Не викликайте
attach()/observe()на сервері, коли працюєте з ядром напряму