Увлажнение приложения SSR React с помощью react-i18next вызывает мерцание

Я довольно долго пытался заставить SSR работать с react-i18next, документации несколько не хватает, поэтому я собрал все, что мог, из некоторых других репозиториев и их пример razzle-ssr.

У меня работает серверная часть, где я:

  1. Настройте экспресс, вызовите соответствующее промежуточное ПО, получите соответствующий языковой стандарт:
const app = express();
await i18n
    .use(Backend)
    .use(i18nextMiddleware.LanguageDetector)
    .init(options)

app.use(i18nextMiddleware.handle(i18n));
app.use('/locales', express.static(`${appDirectory}/locales`));
  1. Получите DOM-представление приложения по запросу:
app.get('/*', req => {
    //...
    const html = ReactDOMServer.renderToString(
        <I18nextProvider i18n={req.i18n}>>
            <App />
        </I18nextProvider>
    )
    // ...
})
  1. Добавьте initialI18nStore к содержанию запроса:
const initialI18nStore = {};
req.i18n.languages.forEach(l => {
  initialI18nStore[l] = req.i18n.services.resourceStore.data[l];
});
const initialLanguage = req.i18n.language;

content = content.replace(
/<head>/,
    `<head>
        <script>
        window.initialI18nStore = "${JSON.stringify(initialI18nStore)}";
        window.initialLanguage = "${initialLanguage.slice(0, 2)}";
    </script>`,
);

Это отлично работает, когда я curl http://localhost:3000/ получаю правильный DOM с загруженными / замененными переводами.

Проблема, с которой я сталкиваюсь, - это гидратация.

Я пробовал использовать useSSR с Suspense, но у меня ничего не получалось. Но я чувствую, что в основе этого возникнет та же проблема: i18n необходимо инициализировать языками, прежде чем мы должны гидратировать приложение. Верный(?)

Я попытался сымитировать то же, что и useSSR, дождавшись инициализации экземпляра client i18n перед гидратированием приложения:

// client i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';

i18n
  .use(initReactI18next)
  .use(Backend)
  .use(LanguageDetector);

const i18nInit = () => {
  return new Promise(resolve => {
    // @todo: shim in moment locale here
    i18n.init(options, () => resolve(i18n));
  });
};

export default i18nInit;
// client index.js
const renderApp = async () => {
  let i18n = await i18ninit();
  if (window.initialI18nStore) {
    i18n.services.resourceStore.data = window.initialI18nStore;
  }
  hydrate(<BaseApp />, document.getElementById('root'));
};

renderApp();

Проблема заключается в следующем: приложение отлично отображает представление DOM, предоставленное сервером. Затем, когда я жду, пока экземпляр клиента i18n инициализируется, а затем увлажняет приложение, я получаю огромное мерцание без стиля, а затем оно возвращает то же представление, что и представление DOM.

Я также попытался сделать отложенный рендеринг внутри функционального компонента:

const BaseApp = () => {
    const [render, setRender] = useState(false);
    useEffect( () => {
        await initI18();
        i18n.services.resourceStore.data = INITIALI18NSTORE;
        setRender(true);
    }, [])
    if(!render) return null;
    return <App />
}

Но это вызывает аналогичное, но вместо мерцания без стиля, белый экран из-за возврата null.

Есть ли что-то, что мне здесь не хватает? Или я что-то не в порядке? Как получить плавный переход от стилей DOM +, предоставленных сервером, к стилям, предоставленным моим клиентом, с включенными переводами?


person tr3online    schedule 06.06.2020    source источник


Ответы (1)


Я попытался воспроизвести вашу проблему. На упомянутом вами шаге:

Я попытался сымитировать то же, что и useSSR, дождавшись инициализации клиентского экземпляра i18n перед гидратированием приложения:

На этом этапе я обнаружил, что результат SSR отличается от результата CSR: SSR против CSR

Это произошло из-за того, что мой язык - zh-TW, и на стороне сервера не предусмотрено «fallbackLng».

Я добавил эту строку, как и i18n.js, и решил проблему

// server.js
i18n
    .use(Backend)
    .use(i18nextMiddleware.LanguageDetector)
    .init(
      {
        debug: false,
        preload: ['en', 'de'],
        fallbackLng: 'en', // << ----- this line
        ns: ['translations'],
        defaultNS: 'translations',
        backend: {
          loadPath: `${appSrc}/locales/{{lng}}/{{ns}}.json`,
          addPath: `${appSrc}/locales/{{lng}}/{{ns}}.missing.json`,
        },
      },
...
...
...

Чтобы убедиться, что клиент отображает правильные модели DOM только в первый раз, я установил useSuspense в значение false и удалил компонент <Suspense>.

// i18n.js
const options = {
  fallbackLng: 'en',
  load: 'languageOnly', // we only provide en, de -> no region specific locals like en-US, de-DE
  // have a common namespace used around the full app
  ns: ['translations'],
  defaultNS: 'translations',

  saveMissing: true,
  debug: true,

  interpolation: {
    escapeValue: false, // not needed for react!!
    formatSeparator: ',',
    format: (value, format, lng) => {
      if (format === 'uppercase') return value.toUpperCase();
      return value;
    },
  },
  react: {
    useSuspense: false,  // << ----- this line
  },
  wait: process && !process.release,
};


// client.js
const BaseApp = () => {
  useSSR(window.initialI18nStore, window.initialLanguage);
  return (
      <BrowserRouter>
        <App />
      </BrowserRouter>
  );
}

И все отлично работает

person Rick Chen    schedule 04.11.2020