SSR-совместимость Pikaday напрямую связана с тем, что библиотека
изначально проектировалась для работы в браузере и опирается на
глобальные объекты window и document. При
серверном рендеринге эти сущности отсутствуют, поэтому любая попытка
инициализации календаря на этапе выполнения кода на сервере приводит к
падению приложения или к некорректному результату гидратации.
Ключевая проблема SSR в контексте Pikaday заключается в том, что
библиотека не разделяет этапы «создание экземпляра» и «подключение к
DOM» достаточно строго. Конструктор new Pikaday() ожидает
наличие DOM-элемента и браузерного окружения, а значит должен
выполняться только на клиенте.
При серверном рендеринге код выполняется в среде Node.js, где отсутствуют:
windowdocumentТипичная ошибка при неаккуратной интеграции:
import Pikaday from 'pikaday';
const picker = new Pikaday({
field: document.getElementById('date') // ❌ document не существует на сервере
});
При выполнении на сервере такой код вызывает
ReferenceError: document is not defined.
Даже если обёртка компонента скрывает доступ к DOM, сам факт импорта и немедленной инициализации библиотеки уже нарушает SSR-цепочку.
Корректная интеграция требует строгого разделения:
Основной принцип — любая работа с Pikaday должна происходить только после mount-фазы.
Самый простой защитный механизм:
const isBrowser = typeof window !== 'undefined';
Однако этого недостаточно, если импорт модуля уже триггерит обращение
к document. Поэтому важнее использовать динамическую
загрузку.
В SSR-архитектурах предпочтительным способом является ленивый импорт:
let Pikaday;
if (typeof window !== 'undefined') {
import('pikaday').then(module => {
Pikaday = module.default;
const picker = new Pikaday({
field: document.getElementById('date')
});
});
}
Такой подход гарантирует, что код библиотеки вообще не попадает в серверный бандл.
В более современных сборках предпочтительнее
await import внутри lifecycle-методов.
В React-экосистеме SSR (например, Next.js) жизненный цикл играет
ключевую роль. Основное правило: инициализация должна происходить внутри
useEffect, который не выполняется на сервере.
import { useEffect, useRef } from 'react';
export default function DatePicker() {
const inputRef = useRef(null);
useEffect(() => {
let picker;
import('pikaday').then(({ default: Pikaday }) => {
picker = new Pikaday({
field: inputRef.current
});
});
return () => {
if (picker) {
picker.destroy();
}
};
}, []);
return <input ref={inputRef} type="text" />;
}
Ключевой момент: useEffect гарантирует отсутствие
выполнения на сервере, что полностью решает проблему SSR-конфликта.
В Next.js часто применяется стратегия полного отключения SSR для конкретного компонента:
import dynamic from 'next/dynamic';
const DatePicker = dynamic(() => import('./DatePicker'), {
ssr: false
});
export default function Page() {
return <DatePicker />;
}
Это наиболее «жёсткий» способ изоляции Pikaday. Он эффективен, но увеличивает клиентскую нагрузку, так как компонент становится полностью клиентским.
Во Vue SSR (Nuxt или кастомные сборки) применяется lifecycle
onMounted:
import { onMounted, ref } from 'vue';
export default {
setup() {
const input = ref(null);
onMounted(async () => {
const { default: Pikaday } = await import('pikaday');
new Pikaday({
field: input.value
});
});
return { input };
}
};
Vue гарантирует, что onMounted выполняется только на
клиенте, что делает этот способ безопасным.
В Angular Universal используется проверка платформы:
import { isPlatformBrowser } from '@angular/common';
import { Inject, PLATFORM_ID, AfterViewInit } from '@angular/core';
declare const Pikaday: any;
export class DateComponent implements AfterViewInit {
constructor(@Inject(PLATFORM_ID) private platformId: object) {}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
const input = document.getElementById('date');
new Pikaday({
field: input
});
}
}
}
Здесь ключевую роль играет AfterViewInit, так как DOM
гарантированно уже существует.
SSR не ограничивается отсутствием window. Вторая
проблема — hydration mismatch.
Pikaday может:
Если сервер уже отрендерил HTML, а клиент сразу изменяет структуру до завершения гидратации, возникают расхождения.
Лучший подход — инициализация только после завершения гидратации:
useEffect(() => {
const timer = setTimeout(() => {
import('pikaday').then(({ default: Pikaday }) => {
new Pikaday({ field: inputRef.current });
});
}, 0);
return () => clearTimeout(timer);
}, []);
Задержка позволяет завершить первичную синхронизацию React/Vue с DOM.
Pikaday активно создаёт вспомогательные DOM-узлы календаря. В SSR-архитектуре важно обеспечить:
Пример:
new Pikaday({
field: inputRef.current,
bound: true,
container: document.body
});
Явное указание контейнера снижает риск расхождения DOM-структур.
В SSG (Static Site Generation) ситуация проще, чем в SSR. Поскольку HTML генерируется один раз и не требует реального времени выполнения на сервере, Pikaday не участвует в генерации вообще.
Однако важно:
Дополнительная сложность — работа с датами:
Это приводит к расхождению отображаемых значений.
Решение:
const initialDate = new Date().toISOString();
Наиболее устойчивый подход — обёртка над Pikaday:
export async function createDatePicker(input) {
if (typeof window === 'undefined') return null;
const { default: Pikaday } = await import('pikaday');
return new Pikaday({
field: input
});
}
Это позволяет централизовать всю SSR-логику и избежать дублирования проверок в компонентах.
Важно учитывать разрушение экземпляра:
picker.destroy();
picker = null;
Без этого в SPA-режиме возникают утечки DOM-событий и дублирование календарей.
Pikaday достаточно лёгкая библиотека, но в SSR-архитектурах важно:
Пример ленивой стратегии:
input.addEventListener('focus', async () => {
const { default: Pikaday } = await import('pikaday');
new Pikaday({ field: input });
}, { once: true });
Такой подход уменьшает нагрузку на initial render и ускоряет TTI.
При streaming SSR (например, React 18) важно учитывать, что часть DOM может быть уже интерактивной, пока остальная страница ещё загружается. Pikaday в таких условиях должен:
Иначе возможны дубли календарей при повторной гидратации сегментов страницы.
Устойчивое поведение Pikaday в SSR-среде достигается комбинацией принципов:
window на сервере