_app.page.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import type { JSX, ReactNode } from 'react';
  2. import React, { useEffect } from 'react';
  3. import type { NextPage } from 'next';
  4. import type { AppContext, AppProps } from 'next/app';
  5. import App from 'next/app';
  6. import { useRouter } from 'next/router';
  7. import type { Locale } from '@growi/core/dist/interfaces';
  8. import { Provider } from 'jotai';
  9. import { appWithTranslation } from 'next-i18next';
  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 {
  15. useHydrateGlobalEachAtoms,
  16. useHydrateGlobalInitialAtoms,
  17. } from '~/states/global/hydrate';
  18. import { swrGlobalConfiguration } from '~/utils/swr-utils';
  19. import type { CommonEachProps, CommonInitialProps } from './common-props';
  20. import { isCommonInitialProps } from './common-props';
  21. import { getLocaleAtServerSide } from './utils/locale';
  22. import { useNextjsRoutingPageRegister } from './utils/nextjs-routing-utils';
  23. import { registerTransformerForObjectId } from './utils/objectid-transformer';
  24. import { deserializeSuperJSONProps } from './utils/superjson-ssr';
  25. import '~/styles/prebuilt/vendor.css';
  26. import '~/styles/style-app.scss';
  27. // register custom serializer
  28. registerTransformerForObjectId();
  29. const StateManagementContainer = ({
  30. children,
  31. }: {
  32. children: ReactNode;
  33. }): JSX.Element => {
  34. return (
  35. <SWRConfig value={swrGlobalConfiguration}>
  36. <Provider>{children}</Provider>
  37. </SWRConfig>
  38. );
  39. };
  40. // biome-ignore lint/complexity/noBannedTypes: Define empty interface to extend later
  41. export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  42. getLayout?: (page: JSX.Element) => ReactNode;
  43. };
  44. type CombinedCommonProps =
  45. | CommonEachProps
  46. | (CommonEachProps & CommonInitialProps);
  47. type GrowiAppProps = AppProps<CombinedCommonProps> & {
  48. Component: NextPageWithLayout<CombinedCommonProps>;
  49. userLocale: Locale;
  50. };
  51. const GrowiAppSubstance = ({
  52. Component,
  53. pageProps: rawPageProps,
  54. userLocale,
  55. }: GrowiAppProps): JSX.Element => {
  56. const router = useRouter();
  57. // Deserialize superjson-serialized props from getServerSideProps
  58. const pageProps = deserializeSuperJSONProps(
  59. rawPageProps,
  60. ) as CombinedCommonProps;
  61. // Hydrate global atoms with server-side data
  62. useHydrateGlobalInitialAtoms(
  63. isCommonInitialProps(pageProps) ? pageProps : undefined,
  64. );
  65. useHydrateGlobalEachAtoms(pageProps);
  66. useNextjsRoutingPageRegister(pageProps.nextjsRoutingPage);
  67. useEffect(() => {
  68. const updateLangAttribute = () => {
  69. if (document.documentElement.getAttribute('lang') !== userLocale) {
  70. document.documentElement.setAttribute('lang', userLocale);
  71. }
  72. };
  73. router.events.on('routeChangeComplete', updateLangAttribute);
  74. return () => {
  75. router.events.off('routeChangeComplete', updateLangAttribute);
  76. };
  77. }, [router, userLocale]);
  78. useEffect(() => {
  79. import('bootstrap/dist/js/bootstrap');
  80. }, []);
  81. // Use the layout defined at the page level, if available
  82. const getLayout = Component.getLayout ?? ((page) => page);
  83. return <>{getLayout(<Component {...pageProps} />)}</>;
  84. };
  85. function GrowiApp(props: GrowiAppProps): JSX.Element {
  86. return (
  87. <>
  88. <GlobalFonts />
  89. <StateManagementContainer>
  90. <GrowiAppSubstance {...props} />
  91. </StateManagementContainer>
  92. </>
  93. );
  94. }
  95. // inject userLocale by context
  96. GrowiApp.getInitialProps = async (appContext: AppContext) => {
  97. const appProps = App.getInitialProps(appContext);
  98. const userLocale = getLocaleAtServerSide(
  99. appContext.ctx.req as unknown as CrowiRequest,
  100. );
  101. return { ...appProps, userLocale };
  102. };
  103. export default appWithTranslation(GrowiApp, nextI18nConfig);