При серверному рендерингу сервер заповнює стори (завантажує дані), рендерить HTML і має передати цей стан у браузер. Інакше на клієнті стори стартують порожніми, Vue перемалює сторінку іншими даними, і гідрація зламається.
Схема:
- на сервері після рендеру взяти весь стан:
pinia.state.value; - серіалізувати з екрануванням і вставити в HTML;
- на клієнті до першого
useStore()відновити його.
// сервер
import { uneval } from 'devalue';
const html = await renderToString(app);
const state = uneval(pinia.state.value);
// <script>window.__pinia = ${state}</script>
// клієнт - перед монтуванням і до будь-якого useStore()
const pinia = createPinia();
app.use(pinia);
if (window.__pinia) {
pinia.state.value = window.__pinia;
}
Чому JSON.stringify - діра. Стан майже завжди містить дані, які вводили користувачі (ім'я, коментар, назва товару). Рядок </script><script>stealCookies()</script> у полі name, вставлений через JSON.stringify у <script>, закриє тег і виконає чужий код. Документація Pinia прямо наголошує: стан треба екранувати, наприклад бібліотекою devalue (її використовує Nuxt), яка ще й зберігає Date, Map, Set.
Що не повинно гідруватися. У setup store можуть бути значення, що мають братися лише з браузера (наприклад, useLocalStorage). Їх позначають skipHydrate(), щоб серверне значення не затерло клієнтське.
Інші правила SSR зі сторами:
- новий екземпляр Pinia на кожен запит - інакше стан одного користувача потрапить у відповідь іншому;
- поза
setup(guard роутера,serverPrefetch) передавати екземпляр явно:useStore(pinia); - не класти в стан секрети: усе, що в сторі на сервері, буде у HTML сторінки;
- браузерні API (
window,localStorage) у сторах - лише в діях, що викликаються в браузері, чи з перевіркою середовища.
У Nuxt усе це зроблено модулем @pinia/nuxt - вручну налаштовувати не треба.