【问题标题】:Hydrating SSR React App with react-i18next causes flicker使用 react-i18next 补水 SSR React App 导致闪烁
【发布时间】:2020-06-06 18:30:48
【问题描述】:

我一直在尝试让 SSR 与 react-i18next 一起工作很长一段时间,但文档有些缺乏,所以我从其他一些 repos 和他们的 razzle-ssr example 中拼凑了我能做到的。

我在服务器端工作,我:

  1. 设置 express,调用适当的中间件,获取适当的语言环境:
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 和加载/替换的翻译时,这很好用

我遇到的问题是补水。

我尝试将useSSRSuspense 一起使用,但似乎无法正常工作。但我觉得这将有基本相同的问题:i18n 需要在我们应该为应用程序补充水分之前使用语言进行初始化。正确(?)

我试图通过等待client i18n 实例在应用程序水合之前被初始化来模拟与useSSR 相同的东西:

// 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+样式无缝过渡到我的客户端提供的包含翻译的样式?

【问题讨论】:

    标签: reactjs server-side-rendering react-i18next


    【解决方案1】:

    我已尝试重现您的问题。在您提到的步骤中:

    我试图通过等待客户端 i18n 实例在应用程序水合之前初始化来模拟与 useSSR 相同的东西:

    在这一步,我发现SSR结果与CSR结果不同:SSR vs 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 并删除了 &lt;Suspense&gt; 组件

    // 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>
      );
    }
    

    一切正常

    【讨论】:

    • 为我工作! ?
    猜你喜欢
    • 2021-01-27
    • 1970-01-01
    • 2020-04-20
    • 2021-08-20
    • 2018-06-01
    • 2020-02-26
    • 1970-01-01
    • 2020-02-22
    • 1970-01-01
    相关资源
    最近更新 更多