_app.page.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import type { ReactNode, JSX } from 'react';
  2. import React, { useEffect } from 'react';
  3. import type { Locale } from '@growi/core/dist/interfaces';
  4. import { Provider } from 'jotai';
  5. import type { NextPage } from 'next';
  6. import { appWithTranslation } from 'next-i18next';
  7. import type { AppContext, AppProps } from 'next/app';
  8. import App from 'next/app';
  9. import { useRouter } from 'next/router';
  10. import { SWRConfig } from 'swr';
  11. import * as nextI18nConfig from '^/config/next-i18next.config';
  12. import { GlobalFonts } from '~/components/FontFamily/GlobalFonts';
  13. import type { CrowiRequest } from '~/interfaces/crowi-request';
  14. import { useHydrateGlobalEachAtoms, useHydrateGlobalInitialAtoms } from '~/states/global/hydrate';
  15. import { swrGlobalConfiguration } from '~/utils/swr-utils';
  16. import type { CommonEachProps, CommonInitialProps } from './common-props';
  17. import { isCommonInitialProps } from './common-props';
  18. import { getLocaleAtServerSide } from './utils/locale';
  19. import { useNextjsRoutingPageRegister } from './utils/nextjs-routing-utils';
  20. import { registerTransformerForObjectId } from './utils/objectid-transformer';
  21. import '~/styles/prebuilt/vendor.css';
  22. import '~/styles/style-app.scss';
  23. // register custom serializer
  24. registerTransformerForObjectId();
  25. const StateManagementContainer = ({ children }: { children: ReactNode }): JSX.Element => {
  26. return (
  27. <SWRConfig value={swrGlobalConfiguration}>
  28. <Provider>
  29. {children}
  30. </Provider>
  31. </SWRConfig>
  32. );
  33. };
  34. // eslint-disable-next-line @typescript-eslint/ban-types
  35. export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  36. getLayout?: (page: JSX.Element) => ReactNode,
  37. }
  38. type CombinedCommonProps = CommonEachProps | (CommonEachProps & CommonInitialProps);
  39. type GrowiAppProps = AppProps<CombinedCommonProps> & {
  40. Component: NextPageWithLayout<CombinedCommonProps>,
  41. userLocale: Locale,
  42. };
  43. const GrowiAppSubstance = ({ Component, pageProps, userLocale }: GrowiAppProps): JSX.Element => {
  44. const router = useRouter();
  45. // Hydrate global atoms with server-side data
  46. useHydrateGlobalInitialAtoms(isCommonInitialProps(pageProps) ? pageProps : undefined);
  47. useHydrateGlobalEachAtoms(pageProps);
  48. useNextjsRoutingPageRegister(pageProps.nextjsRoutingPage);
  49. useEffect(() => {
  50. const updateLangAttribute = () => {
  51. if (document.documentElement.getAttribute('lang') !== userLocale) {
  52. document.documentElement.setAttribute('lang', userLocale);
  53. }
  54. };
  55. router.events.on('routeChangeComplete', updateLangAttribute);
  56. return () => {
  57. router.events.off('routeChangeComplete', updateLangAttribute);
  58. };
  59. }, [router, userLocale]);
  60. useEffect(() => {
  61. import('bootstrap/dist/js/bootstrap');
  62. }, []);
  63. // Use the layout defined at the page level, if available
  64. const getLayout = Component.getLayout ?? (page => page);
  65. return <>{getLayout(<Component {...pageProps} />)}</>;
  66. };
  67. function GrowiApp(props: GrowiAppProps): JSX.Element {
  68. return (
  69. <>
  70. <GlobalFonts />
  71. <StateManagementContainer>
  72. <GrowiAppSubstance {...props} />
  73. </StateManagementContainer>
  74. </>
  75. );
  76. }
  77. // inject userLocale by context
  78. GrowiApp.getInitialProps = async(appContext: AppContext) => {
  79. const appProps = App.getInitialProps(appContext);
  80. const userLocale = getLocaleAtServerSide(appContext.ctx.req as unknown as CrowiRequest);
  81. return { ...appProps, userLocale };
  82. };
  83. export default appWithTranslation(GrowiApp, nextI18nConfig);