Prechádzať zdrojové kódy

Merge pull request #10278 from weseek/feat/use-jotai-for-share-link

feat: Use jotai for share link
Yuki Takei 7 mesiacov pred
rodič
commit
636626abe4
46 zmenil súbory, kde vykonal 922 pridanie a 667 odobranie
  1. BIN
      apps/app/.swc/plugins/v7_linux_x86_64_0.106.16/85face98bcf0ea217842cb93383692497c6ae8a7da71a76bf3d93a3a42b4228c
  2. 1 0
      apps/app/next.config.js
  3. 2 2
      apps/app/package.json
  4. 2 3
      apps/app/src/client/components/DescendantsPageList.tsx
  5. 2 2
      apps/app/src/client/components/DescendantsPageListModal.tsx
  6. 7 8
      apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx
  7. 2 2
      apps/app/src/client/components/Navbar/GrowiNavbarBottom.tsx
  8. 2 3
      apps/app/src/client/components/PageAccessoriesModal/PageAccessoriesModal.tsx
  9. 2 3
      apps/app/src/client/components/PageControls/PageControls.tsx
  10. 4 7
      apps/app/src/client/components/ReactMarkdownComponents/DrawioViewerWithEditButton.tsx
  11. 4 6
      apps/app/src/client/components/ReactMarkdownComponents/Header.tsx
  12. 4 6
      apps/app/src/client/components/ReactMarkdownComponents/TableWithEditButton.tsx
  13. 3 3
      apps/app/src/client/components/Sidebar/Sidebar.tsx
  14. 2 2
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  15. 2 2
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  16. 3 3
      apps/app/src/components/PageView/PageView.tsx
  17. 10 9
      apps/app/src/components/ShareLinkPageView/ShareLinkPageView.tsx
  18. 9 5
      apps/app/src/pages/[[...path]]/index.page.tsx
  19. 13 4
      apps/app/src/pages/[[...path]]/page-data-props.ts
  20. 7 6
      apps/app/src/pages/[[...path]]/server-side-props.ts
  21. 10 0
      apps/app/src/pages/[[...path]]/types.ts
  22. 0 0
      apps/app/src/pages/[[...path]]/use-same-route-navigation.spec.tsx
  23. 0 0
      apps/app/src/pages/[[...path]]/use-same-route-navigation.ts
  24. 2 2
      apps/app/src/pages/[[...path]]/use-shallow-routing.ts
  25. 26 15
      apps/app/src/pages/_app.page.tsx
  26. 40 0
      apps/app/src/pages/common-props/commons.ts
  27. 1 1
      apps/app/src/pages/common-props/index.ts
  28. 1 2
      apps/app/src/pages/general-page/index.ts
  29. 6 6
      apps/app/src/pages/general-page/superjson/page-to-show-revision-with-meta.ts
  30. 6 46
      apps/app/src/pages/general-page/type-guards.ts
  31. 2 13
      apps/app/src/pages/general-page/types.ts
  32. 51 87
      apps/app/src/pages/login/index.page.tsx
  33. 1 0
      apps/app/src/pages/share/[[...path]]/consts/index.ts
  34. 92 203
      apps/app/src/pages/share/[[...path]]/index.page.tsx
  35. 93 0
      apps/app/src/pages/share/[[...path]]/page-data-props.ts
  36. 141 0
      apps/app/src/pages/share/[[...path]]/server-side-props.ts
  37. 18 0
      apps/app/src/pages/share/[[...path]]/types.ts
  38. 8 8
      apps/app/src/states/global/hydrate.ts
  39. 22 12
      apps/app/src/states/page/hooks.ts
  40. 19 43
      apps/app/src/states/page/hydrate.ts
  41. 13 6
      apps/app/src/states/page/internal-atoms.ts
  42. 126 105
      apps/app/src/states/page/use-fetch-current-page.ts
  43. 0 12
      apps/app/src/stores-universal/context.tsx
  44. 2 2
      apps/app/src/stores/page.tsx
  45. 8 7
      apps/app/src/stores/ui.tsx
  46. 153 21
      pnpm-lock.yaml

BIN
apps/app/.swc/plugins/v7_linux_x86_64_0.106.16/85face98bcf0ea217842cb93383692497c6ae8a7da71a76bf3d93a3a42b4228c


+ 1 - 0
apps/app/next.config.js

@@ -58,6 +58,7 @@ const getTranspilePackages = () => {
     'github-slugger',
     'github-slugger',
     'html-url-attributes',
     'html-url-attributes',
     'estree-util-is-identifier-name',
     'estree-util-is-identifier-name',
+    'superjson',
     ...listPrefixedPackages([
     ...listPrefixedPackages([
       'remark-',
       'remark-',
       'rehype-',
       'rehype-',

+ 2 - 2
apps/app/package.json

@@ -177,7 +177,7 @@
     "next": "^14.2.30",
     "next": "^14.2.30",
     "next-dynamic-loading-props": "^0.1.1",
     "next-dynamic-loading-props": "^0.1.1",
     "next-i18next": "^15.3.1",
     "next-i18next": "^15.3.1",
-    "next-superjson": "^0.0.4",
+    "next-superjson": "^1.0.7",
     "next-themes": "^0.2.1",
     "next-themes": "^0.2.1",
     "nocache": "^4.0.0",
     "nocache": "^4.0.0",
     "node-cron": "^3.0.2",
     "node-cron": "^3.0.2",
@@ -232,7 +232,7 @@
     "sanitize-filename": "^1.6.3",
     "sanitize-filename": "^1.6.3",
     "socket.io": "^4.7.5",
     "socket.io": "^4.7.5",
     "string-width": "=4.2.2",
     "string-width": "=4.2.2",
-    "superjson": "^1.9.1",
+    "superjson": "^2.2.2",
     "swagger-jsdoc": "^6.2.8",
     "swagger-jsdoc": "^6.2.8",
     "swr": "^2.3.2",
     "swr": "^2.3.2",
     "throttle-debounce": "^5.0.0",
     "throttle-debounce": "^5.0.0",

+ 2 - 3
apps/app/src/client/components/DescendantsPageList.tsx

@@ -11,8 +11,7 @@ import { useTranslation } from 'next-i18next';
 import { toastSuccess } from '~/client/util/toastr';
 import { toastSuccess } from '~/client/util/toastr';
 import type { IPagingResult } from '~/interfaces/paging-result';
 import type { IPagingResult } from '~/interfaces/paging-result';
 import type { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
 import type { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
-import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
-import { useIsSharedUser } from '~/stores-universal/context';
+import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/states/context';
 import {
 import {
   mutatePageTree,
   mutatePageTree,
   useSWRxPageInfoForList, useSWRxPageList, mutateRecentlyUpdated,
   useSWRxPageInfoForList, useSWRxPageList, mutateRecentlyUpdated,
@@ -134,7 +133,7 @@ export const DescendantsPageList = (props: DescendantsPageListProps): JSX.Elemen
 
 
   const [activePage, setActivePage] = useState(1);
   const [activePage, setActivePage] = useState(1);
 
 
-  const { data: isSharedUser } = useIsSharedUser();
+  const [isSharedUser] = useIsSharedUser();
 
 
   const { data: pagingResult, error, mutate } = useSWRxPageList(isSharedUser ? null : path, activePage, limit);
   const { data: pagingResult, error, mutate } = useSWRxPageList(isSharedUser ? null : path, activePage, limit);
 
 

+ 2 - 2
apps/app/src/client/components/DescendantsPageListModal.tsx

@@ -10,7 +10,7 @@ import {
   Modal, ModalHeader, ModalBody,
   Modal, ModalHeader, ModalBody,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
-import { useIsSharedUser } from '~/stores-universal/context';
+import { useIsSharedUser } from '~/states/context';
 import { useDescendantsPageListModal } from '~/stores/modal';
 import { useDescendantsPageListModal } from '~/stores/modal';
 import { useIsDeviceLargerThanLg } from '~/stores/ui';
 import { useIsDeviceLargerThanLg } from '~/stores/ui';
 
 
@@ -31,7 +31,7 @@ export const DescendantsPageListModal = (): JSX.Element => {
   const [activeTab, setActiveTab] = useState('pagelist');
   const [activeTab, setActiveTab] = useState('pagelist');
   const [isWindowExpanded, setIsWindowExpanded] = useState(false);
   const [isWindowExpanded, setIsWindowExpanded] = useState(false);
 
 
-  const { data: isSharedUser } = useIsSharedUser();
+  const [isSharedUser] = useIsSharedUser();
 
 
   const { data: status, close } = useDescendantsPageListModal();
   const { data: status, close } = useDescendantsPageListModal();
 
 

+ 7 - 8
apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -24,14 +24,15 @@ import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import { usePageBulkExportSelectModal } from '~/features/page-bulk-export/client/stores/modal';
 import { usePageBulkExportSelectModal } from '~/features/page-bulk-export/client/stores/modal';
 import type { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import type { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
-import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
+import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/states/context';
 import { useCurrentPathname, useCurrentUser } from '~/states/global';
 import { useCurrentPathname, useCurrentUser } from '~/states/global';
 import { useCurrentPageId, useFetchCurrentPage } from '~/states/page';
 import { useCurrentPageId, useFetchCurrentPage } from '~/states/page';
+import { useShareLinkId } from '~/states/page/hooks';
 import {
 import {
+  useDisableLinkSharing,
   useIsBulkExportPagesEnabled, useIsLocalAccountRegistrationEnabled, useIsUploadEnabled,
   useIsBulkExportPagesEnabled, useIsLocalAccountRegistrationEnabled, useIsUploadEnabled,
 } from '~/states/server-configurations';
 } from '~/states/server-configurations';
 import { useEditorMode } from '~/states/ui/editor';
 import { useEditorMode } from '~/states/ui/editor';
-import { useIsSharedUser, useShareLinkId } from '~/stores-universal/context';
 import {
 import {
   usePageAccessoriesModal, PageAccessoriesModalContents, type IPageForPageDuplicateModal,
   usePageAccessoriesModal, PageAccessoriesModalContents, type IPageForPageDuplicateModal,
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal, usePagePresentationModal,
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal, usePagePresentationModal,
@@ -80,7 +81,7 @@ const PageOperationMenuItems = (props: PageOperationMenuItemsProps): JSX.Element
 
 
   const [isGuestUser] = useIsGuestUser();
   const [isGuestUser] = useIsGuestUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
-  const { data: isSharedUser } = useIsSharedUser();
+  const [isSharedUser] = useIsSharedUser();
   const [isBulkExportPagesEnabled] = useIsBulkExportPagesEnabled();
   const [isBulkExportPagesEnabled] = useIsBulkExportPagesEnabled();
   const [isUploadEnabled] = useIsUploadEnabled();
   const [isUploadEnabled] = useIsUploadEnabled();
 
 
@@ -244,7 +245,6 @@ const CreateTemplateMenuItems = (props: CreateTemplateMenuItemsProps): JSX.Eleme
 
 
 type GrowiContextualSubNavigationProps = {
 type GrowiContextualSubNavigationProps = {
   currentPage?: IPagePopulatedToShowRevision | null,
   currentPage?: IPagePopulatedToShowRevision | null,
-  isLinkSharingDisabled?: boolean,
 };
 };
 
 
 const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps): JSX.Element => {
 const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps): JSX.Element => {
@@ -255,7 +255,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
 
   const router = useRouter();
   const router = useRouter();
 
 
-  const { data: shareLinkId } = useShareLinkId();
+  const [shareLinkId] = useShareLinkId();
   const { fetchCurrentPage } = useFetchCurrentPage();
   const { fetchCurrentPage } = useFetchCurrentPage();
 
 
   const [currentPathname] = useCurrentPathname();
   const [currentPathname] = useCurrentPathname();
@@ -270,7 +270,8 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const [isGuestUser] = useIsGuestUser();
   const [isGuestUser] = useIsGuestUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const [isLocalAccountRegistrationEnabled] = useIsLocalAccountRegistrationEnabled();
   const [isLocalAccountRegistrationEnabled] = useIsLocalAccountRegistrationEnabled();
-  const { data: isSharedUser } = useIsSharedUser();
+  const [isLinkSharingDisabled] = useDisableLinkSharing();
+  const [isSharedUser] = useIsSharedUser();
 
 
   const shouldExpandContent = useShouldExpandContent(currentPage);
   const shouldExpandContent = useShouldExpandContent(currentPage);
 
 
@@ -292,8 +293,6 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
 
   const [isPageTemplateModalShown, setIsPageTempleteModalShown] = useState(false);
   const [isPageTemplateModalShown, setIsPageTempleteModalShown] = useState(false);
 
 
-  const { isLinkSharingDisabled } = props;
-
   const duplicateItemClickedHandler = useCallback(async(page: IPageForPageDuplicateModal) => {
   const duplicateItemClickedHandler = useCallback(async(page: IPageForPageDuplicateModal) => {
     const duplicatedHandler: OnDuplicatedFunction = (fromPath, toPath) => {
     const duplicatedHandler: OnDuplicatedFunction = (fromPath, toPath) => {
       router.push(toPath);
       router.push(toPath);

+ 2 - 2
apps/app/src/client/components/Navbar/GrowiNavbarBottom.tsx

@@ -2,9 +2,9 @@ import React, { useCallback, type JSX } from 'react';
 
 
 import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import { GroundGlassBar } from '~/components/Navbar/GroundGlassBar';
 import { useSearchModal } from '~/features/search/client/stores/search';
 import { useSearchModal } from '~/features/search/client/stores/search';
+import { useIsSearchPage } from '~/states/context';
 import { useCurrentPagePath } from '~/states/page';
 import { useCurrentPagePath } from '~/states/page';
 import { useDrawerOpened } from '~/states/ui/sidebar';
 import { useDrawerOpened } from '~/states/ui/sidebar';
-import { useIsSearchPage } from '~/stores-universal/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { usePageCreateModal } from '~/stores/modal';
 
 
 import styles from './GrowiNavbarBottom.module.scss';
 import styles from './GrowiNavbarBottom.module.scss';
@@ -15,7 +15,7 @@ export const GrowiNavbarBottom = (): JSX.Element => {
   const [isDrawerOpened, setIsDrawerOpened] = useDrawerOpened();
   const [isDrawerOpened, setIsDrawerOpened] = useDrawerOpened();
   const { open: openCreateModal } = usePageCreateModal();
   const { open: openCreateModal } = usePageCreateModal();
   const [currentPagePath] = useCurrentPagePath();
   const [currentPagePath] = useCurrentPagePath();
-  const { data: isSearchPage } = useIsSearchPage();
+  const [isSearchPage] = useIsSearchPage();
   const { open: openSearchModal } = useSearchModal();
   const { open: openSearchModal } = useSearchModal();
 
 
   const searchButtonClickHandler = useCallback(() => {
   const searchButtonClickHandler = useCallback(() => {

+ 2 - 3
apps/app/src/client/components/PageAccessoriesModal/PageAccessoriesModal.tsx

@@ -6,9 +6,8 @@ import {
   Modal, ModalBody, ModalHeader,
   Modal, ModalBody, ModalHeader,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
-import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
+import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/states/context';
 import { useDisableLinkSharing } from '~/states/server-configurations';
 import { useDisableLinkSharing } from '~/states/server-configurations';
-import { useIsSharedUser } from '~/stores-universal/context';
 import { usePageAccessoriesModal, PageAccessoriesModalContents } from '~/stores/modal';
 import { usePageAccessoriesModal, PageAccessoriesModalContents } from '~/stores/modal';
 import { useIsDeviceLargerThanLg } from '~/stores/ui';
 import { useIsDeviceLargerThanLg } from '~/stores/ui';
 
 
@@ -32,7 +31,7 @@ export const PageAccessoriesModal = (): JSX.Element => {
 
 
   const [isWindowExpanded, setIsWindowExpanded] = useState(false);
   const [isWindowExpanded, setIsWindowExpanded] = useState(false);
 
 
-  const { data: isSharedUser } = useIsSharedUser();
+  const [isSharedUser] = useIsSharedUser();
   const [isGuestUser] = useIsGuestUser();
   const [isGuestUser] = useIsGuestUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const [isLinkSharingDisabled] = useDisableLinkSharing();
   const [isLinkSharingDisabled] = useDisableLinkSharing();

+ 2 - 3
apps/app/src/client/components/PageControls/PageControls.tsx

@@ -18,13 +18,12 @@ import {
 } from '~/client/services/page-operation';
 } from '~/client/services/page-operation';
 import { toastError } from '~/client/util/toastr';
 import { toastError } from '~/client/util/toastr';
 import OpenDefaultAiAssistantButton from '~/features/openai/client/components/AiAssistant/OpenDefaultAiAssistantButton';
 import OpenDefaultAiAssistantButton from '~/features/openai/client/components/AiAssistant/OpenDefaultAiAssistantButton';
-import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
+import { useIsGuestUser, useIsReadOnlyUser, useIsSearchPage } from '~/states/context';
 import { useCurrentPagePath } from '~/states/page';
 import { useCurrentPagePath } from '~/states/page';
 import { useIsUsersHomepageDeletionEnabled } from '~/states/server-configurations';
 import { useIsUsersHomepageDeletionEnabled } from '~/states/server-configurations';
 import {
 import {
   EditorMode, useEditorMode,
   EditorMode, useEditorMode,
 } from '~/states/ui/editor';
 } from '~/states/ui/editor';
-import { useIsSearchPage } from '~/stores-universal/context';
 import { useTagEditModal, type IPageForPageDuplicateModal } from '~/stores/modal';
 import { useTagEditModal, type IPageForPageDuplicateModal } from '~/stores/modal';
 import {
 import {
   useIsDeviceLargerThanMd, usePageControlsX,
   useIsDeviceLargerThanMd, usePageControlsX,
@@ -137,7 +136,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const { editorMode } = useEditorMode();
   const { editorMode } = useEditorMode();
   const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
   const { data: isDeviceLargerThanMd } = useIsDeviceLargerThanMd();
-  const { data: isSearchPage } = useIsSearchPage();
+  const [isSearchPage] = useIsSearchPage();
   const [isUsersHomepageDeletionEnabled] = useIsUsersHomepageDeletionEnabled();
   const [isUsersHomepageDeletionEnabled] = useIsUsersHomepageDeletionEnabled();
   const [currentPagePath] = useCurrentPagePath();
   const [currentPagePath] = useCurrentPagePath();
 
 

+ 4 - 7
apps/app/src/client/components/ReactMarkdownComponents/DrawioViewerWithEditButton.tsx

@@ -9,17 +9,14 @@ import {
 } from '@growi/remark-drawio';
 } from '@growi/remark-drawio';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
+import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/states/context';
 import { useIsRevisionOutdated } from '~/states/page';
 import { useIsRevisionOutdated } from '~/states/page';
-import {
-  useIsSharedUser, useShareLinkId,
-} from '~/stores-universal/context';
+import { useShareLinkId } from '~/states/page/hooks';
 import { useCurrentPageYjsData } from '~/stores/yjs';
 import { useCurrentPageYjsData } from '~/stores/yjs';
 
 
 import '@growi/remark-drawio/dist/style.css';
 import '@growi/remark-drawio/dist/style.css';
 import styles from './DrawioViewerWithEditButton.module.scss';
 import styles from './DrawioViewerWithEditButton.module.scss';
 
 
-import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
-
 
 
 declare global {
 declare global {
   // eslint-disable-next-line vars-on-top, no-var
   // eslint-disable-next-line vars-on-top, no-var
@@ -34,8 +31,8 @@ export const DrawioViewerWithEditButton = React.memo((props: DrawioViewerProps):
 
 
   const [isGuestUser] = useIsGuestUser();
   const [isGuestUser] = useIsGuestUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
-  const { data: isSharedUser } = useIsSharedUser();
-  const { data: shareLinkId } = useShareLinkId();
+  const [isSharedUser] = useIsSharedUser();
+  const [shareLinkId] = useShareLinkId();
   const [isRevisionOutdated] = useIsRevisionOutdated();
   const [isRevisionOutdated] = useIsRevisionOutdated();
   const { data: currentPageYjsData } = useCurrentPageYjsData();
   const { data: currentPageYjsData } = useCurrentPageYjsData();
 
 

+ 4 - 6
apps/app/src/client/components/ReactMarkdownComponents/Header.tsx

@@ -8,10 +8,8 @@ import type { Element } from 'hast';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
 
 
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
 import { NextLink } from '~/components/ReactMarkdownComponents/NextLink';
-import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
-import {
-  useIsSharedUser, useShareLinkId,
-} from '~/stores-universal/context';
+import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/states/context';
+import { useShareLinkId } from '~/states/page/hooks';
 import { useCurrentPageYjsData } from '~/stores/yjs';
 import { useCurrentPageYjsData } from '~/stores/yjs';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -67,8 +65,8 @@ export const Header = (props: HeaderProps): JSX.Element => {
 
 
   const [isGuestUser] = useIsGuestUser();
   const [isGuestUser] = useIsGuestUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
-  const { data: isSharedUser } = useIsSharedUser();
-  const { data: shareLinkId } = useShareLinkId();
+  const [isSharedUser] = useIsSharedUser();
+  const [shareLinkId] = useShareLinkId();
   const { data: currentPageYjsData, isLoading: isLoadingCurrentPageYjsData } = useCurrentPageYjsData();
   const { data: currentPageYjsData, isLoading: isLoadingCurrentPageYjsData } = useCurrentPageYjsData();
 
 
   const router = useRouter();
   const router = useRouter();

+ 4 - 6
apps/app/src/client/components/ReactMarkdownComponents/TableWithEditButton.tsx

@@ -4,11 +4,9 @@ import type EventEmitter from 'events';
 
 
 import type { Element } from 'hast';
 import type { Element } from 'hast';
 
 
-import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
+import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/states/context';
 import { useIsRevisionOutdated } from '~/states/page';
 import { useIsRevisionOutdated } from '~/states/page';
-import {
-  useIsSharedUser, useShareLinkId,
-} from '~/stores-universal/context';
+import { useShareLinkId } from '~/states/page/hooks';
 import { useCurrentPageYjsData } from '~/stores/yjs';
 import { useCurrentPageYjsData } from '~/stores/yjs';
 
 
 import styles from './TableWithEditButton.module.scss';
 import styles from './TableWithEditButton.module.scss';
@@ -29,8 +27,8 @@ const TableWithEditButtonNoMemorized = (props: TableWithEditButtonProps): JSX.El
 
 
   const [isGuestUser] = useIsGuestUser();
   const [isGuestUser] = useIsGuestUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
   const [isReadOnlyUser] = useIsReadOnlyUser();
-  const { data: isSharedUser } = useIsSharedUser();
-  const { data: shareLinkId } = useShareLinkId();
+  const [isSharedUser] = useIsSharedUser();
+  const [shareLinkId] = useShareLinkId();
   const [isRevisionOutdated] = useIsRevisionOutdated();
   const [isRevisionOutdated] = useIsRevisionOutdated();
   const { data: currentPageYjsData } = useCurrentPageYjsData();
   const { data: currentPageYjsData } = useCurrentPageYjsData();
 
 

+ 3 - 3
apps/app/src/client/components/Sidebar/Sidebar.tsx

@@ -8,7 +8,9 @@ import SimpleBar from 'simplebar-react';
 import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
 
 import { SidebarMode } from '~/interfaces/ui';
 import { SidebarMode } from '~/interfaces/ui';
+import { useIsSearchPage } from '~/states/context';
 import { useDeviceLargerThanXl } from '~/states/ui/device';
 import { useDeviceLargerThanXl } from '~/states/ui/device';
+import { EditorMode, useEditorMode } from '~/states/ui/editor';
 import {
 import {
   useDrawerOpened,
   useDrawerOpened,
   usePreferCollapsedMode,
   usePreferCollapsedMode,
@@ -16,8 +18,6 @@ import {
   useCollapsedContentsOpened,
   useCollapsedContentsOpened,
   useCurrentProductNavWidth,
   useCurrentProductNavWidth,
 } from '~/states/ui/sidebar';
 } from '~/states/ui/sidebar';
-import { useIsSearchPage } from '~/stores-universal/context';
-import { EditorMode, useEditorMode } from '~/states/ui/editor';
 import {
 import {
   useSidebarScrollerRef,
   useSidebarScrollerRef,
   useIsDeviceLargerThanMd,
   useIsDeviceLargerThanMd,
@@ -232,7 +232,7 @@ export const Sidebar = (): JSX.Element => {
     isDrawerMode, isCollapsedMode, isDockMode,
     isDrawerMode, isCollapsedMode, isDockMode,
   } = useSidebarMode();
   } = useSidebarMode();
 
 
-  const { data: isSearchPage } = useIsSearchPage();
+  const [isSearchPage] = useIsSearchPage();
   const { editorMode } = useEditorMode();
   const { editorMode } = useEditorMode();
   const { data: isMdSize } = useIsDeviceLargerThanMd();
   const { data: isMdSize } = useIsDeviceLargerThanMd();
   const [isXlSize] = useDeviceLargerThanXl();
   const [isXlSize] = useDeviceLargerThanXl();

+ 2 - 2
apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts

@@ -8,7 +8,7 @@ import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 import { replaceDrawioInMarkdown } from '~/client/components/Page/markdown-drawio-util-for-view';
 import { replaceDrawioInMarkdown } from '~/client/components/Page/markdown-drawio-util-for-view';
 import { extractRemoteRevisionDataFromErrorObj, useUpdatePage } from '~/client/services/update-page';
 import { extractRemoteRevisionDataFromErrorObj, useUpdatePage } from '~/client/services/update-page';
 import { useCurrentPageData } from '~/states/page';
 import { useCurrentPageData } from '~/states/page';
-import { useShareLinkId } from '~/stores-universal/context';
+import { useShareLinkId } from '~/states/page/hooks';
 import { useConflictDiffModal, useDrawioModal } from '~/stores/modal';
 import { useConflictDiffModal, useDrawioModal } from '~/stores/modal';
 import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -28,7 +28,7 @@ export const useDrawioModalLauncherForView = (opts?: {
   onSaveError?: (error: any) => void,
   onSaveError?: (error: any) => void,
 }): void => {
 }): void => {
 
 
-  const { data: shareLinkId } = useShareLinkId();
+  const [shareLinkId] = useShareLinkId();
 
 
   const [currentPage] = useCurrentPageData();
   const [currentPage] = useCurrentPageData();
 
 

+ 2 - 2
apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts

@@ -8,7 +8,7 @@ import type { MarkdownTable } from '@growi/editor';
 import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/client/components/Page/markdown-table-util-for-view';
 import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/client/components/Page/markdown-table-util-for-view';
 import { extractRemoteRevisionDataFromErrorObj, useUpdatePage } from '~/client/services/update-page';
 import { extractRemoteRevisionDataFromErrorObj, useUpdatePage } from '~/client/services/update-page';
 import { useCurrentPageData } from '~/states/page';
 import { useCurrentPageData } from '~/states/page';
-import { useShareLinkId } from '~/stores-universal/context';
+import { useShareLinkId } from '~/states/page/hooks';
 import { useHandsontableModal, useConflictDiffModal } from '~/stores/modal';
 import { useHandsontableModal, useConflictDiffModal } from '~/stores/modal';
 import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -28,7 +28,7 @@ export const useHandsontableModalLauncherForView = (opts?: {
   onSaveError?: (error: any) => void,
   onSaveError?: (error: any) => void,
 }): void => {
 }): void => {
 
 
-  const { data: shareLinkId } = useShareLinkId();
+  const [shareLinkId] = useShareLinkId();
 
 
   const [currentPage] = useCurrentPageData();
   const [currentPage] = useCurrentPageData();
 
 

+ 3 - 3
apps/app/src/components/PageView/PageView.tsx

@@ -1,5 +1,5 @@
-import React, {
-  useEffect, useMemo, useRef, type JSX,
+import {
+  useEffect, useMemo, useRef, memo, type JSX,
 } from 'react';
 } from 'react';
 
 
 import { isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
 import { isUsersHomepage } from '@growi/core/dist/utils/page-path-utils';
@@ -40,7 +40,7 @@ type Props = {
   className?: string,
   className?: string,
 }
 }
 
 
-export const PageView = React.memo((props: Props): JSX.Element => {
+export const PageView = memo((props: Props): JSX.Element => {
   const renderStartTime = performance.now();
   const renderStartTime = performance.now();
 
 
   const commentsContainerRef = useRef<HTMLDivElement>(null);
   const commentsContainerRef = useRef<HTMLDivElement>(null);

+ 10 - 9
apps/app/src/components/ShareLinkPageView/ShareLinkPageView.tsx

@@ -1,6 +1,5 @@
-import { useMemo, type JSX } from 'react';
+import { useMemo, memo, type JSX } from 'react';
 
 
-import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
 import { useSlidesByFrontmatter } from '@growi/presentation/dist/services';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 
 
@@ -9,7 +8,7 @@ import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { IShareLinkHasId } from '~/interfaces/share-link';
 import type { IShareLinkHasId } from '~/interfaces/share-link';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
 import { useShouldExpandContent } from '~/services/layout/use-should-expand-content';
 import { generateSSRViewOptions } from '~/services/renderer/renderer';
 import { generateSSRViewOptions } from '~/services/renderer/renderer';
-import { usePageNotFound } from '~/states/page';
+import { useCurrentPageData, usePageNotFound } from '~/states/page';
 import { useViewOptions } from '~/stores/renderer';
 import { useViewOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
@@ -20,7 +19,7 @@ import RevisionRenderer from '../PageView/RevisionRenderer';
 import ShareLinkAlert from './ShareLinkAlert';
 import ShareLinkAlert from './ShareLinkAlert';
 
 
 
 
-const logger = loggerFactory('growi:Page');
+const logger = loggerFactory('growi:components:ShareLinkPageView');
 
 
 
 
 const PageSideContents = dynamic(() => import('~/client/components/PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
 const PageSideContents = dynamic(() => import('~/client/components/PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
@@ -30,21 +29,22 @@ const SlideRenderer = dynamic(() => import('~/client/components/Page/SlideRender
 type Props = {
 type Props = {
   pagePath: string,
   pagePath: string,
   rendererConfig: RendererConfig,
   rendererConfig: RendererConfig,
-  page?: IPagePopulatedToShowRevision,
   shareLink?: IShareLinkHasId,
   shareLink?: IShareLinkHasId,
-  isExpired: boolean,
+  isExpired?: boolean,
   disableLinkSharing: boolean,
   disableLinkSharing: boolean,
 }
 }
 
 
-export const ShareLinkPageView = (props: Props): JSX.Element => {
+export const ShareLinkPageView = memo((props: Props): JSX.Element => {
   const {
   const {
     pagePath, rendererConfig,
     pagePath, rendererConfig,
-    page, shareLink,
+    shareLink,
     isExpired, disableLinkSharing,
     isExpired, disableLinkSharing,
   } = props;
   } = props;
 
 
   const [isNotFoundMeta] = usePageNotFound();
   const [isNotFoundMeta] = usePageNotFound();
 
 
+  const [page] = useCurrentPageData();
+
   const { data: viewOptions } = useViewOptions();
   const { data: viewOptions } = useViewOptions();
 
 
   const shouldExpandContent = useShouldExpandContent(page);
   const shouldExpandContent = useShouldExpandContent(page);
@@ -128,4 +128,5 @@ export const ShareLinkPageView = (props: Props): JSX.Element => {
       ) }
       ) }
     </PageViewLayout>
     </PageViewLayout>
   );
   );
-};
+});
+ShareLinkPageView.displayName = 'ShareLinkPageView';

+ 9 - 5
apps/app/src/pages/[[...path]]/index.page.tsx

@@ -32,16 +32,17 @@ import { useHydrateSidebarAtoms } from '~/states/ui/sidebar/hydrate';
 import { useSWRMUTxCurrentPageYjsData } from '~/stores/yjs';
 import { useSWRMUTxCurrentPageYjsData } from '~/stores/yjs';
 
 
 import type { NextPageWithLayout } from '../_app.page';
 import type { NextPageWithLayout } from '../_app.page';
-import type {
-  Props, InitialProps, SameRouteEachProps,
-} from '../general-page';
-import { useInitialCSRFetch, useSameRouteNavigation } from '../general-page';
+import type { UserUISettingsProps } from '../common-props';
+import type { InitialProps, SidebarConfigProps } from '../general-page';
+import { useInitialCSRFetch } from '../general-page';
 import { registerPageToShowRevisionWithMeta } from '../general-page/superjson';
 import { registerPageToShowRevisionWithMeta } from '../general-page/superjson';
 import { NextjsRoutingType, detectNextjsRoutingType } from '../utils/nextjs-routing-utils';
 import { NextjsRoutingType, detectNextjsRoutingType } from '../utils/nextjs-routing-utils';
 import { useCustomTitleForPage } from '../utils/page-title-customization';
 import { useCustomTitleForPage } from '../utils/page-title-customization';
 
 
 import { NEXT_JS_ROUTING_PAGE } from './consts';
 import { NEXT_JS_ROUTING_PAGE } from './consts';
 import { getServerSidePropsForInitial, getServerSidePropsForSameRoute } from './server-side-props';
 import { getServerSidePropsForInitial, getServerSidePropsForSameRoute } from './server-side-props';
+import type { EachProps } from './types';
+import { useSameRouteNavigation } from './use-same-route-navigation';
 import { useShallowRouting } from './use-shallow-routing';
 import { useShallowRouting } from './use-shallow-routing';
 
 
 // call superjson custom register
 // call superjson custom register
@@ -74,10 +75,13 @@ const ConflictDiffModal = dynamic(() => import('~/client/components/PageEditor/C
 const EditablePageEffects = dynamic(() => import('~/client/components/Page/EditablePageEffects').then(mod => mod.EditablePageEffects), { ssr: false });
 const EditablePageEffects = dynamic(() => import('~/client/components/Page/EditablePageEffects').then(mod => mod.EditablePageEffects), { ssr: false });
 
 
 
 
-const isInitialProps = (props: Props): props is (InitialProps & SameRouteEachProps) => {
+type Props = EachProps | (EachProps & InitialProps & UserUISettingsProps & SidebarConfigProps);
+
+const isInitialProps = (props: Props): props is (UserUISettingsProps & SidebarConfigProps & InitialProps & EachProps) => {
   return 'isNextjsRoutingTypeInitial' in props && props.isNextjsRoutingTypeInitial;
   return 'isNextjsRoutingTypeInitial' in props && props.isNextjsRoutingTypeInitial;
 };
 };
 
 
+
 const Page: NextPageWithLayout<Props> = (props: Props) => {
 const Page: NextPageWithLayout<Props> = (props: Props) => {
 
 
   // register global EventEmitter
   // register global EventEmitter

+ 13 - 4
apps/app/src/pages/[[...path]]/page-data-props.ts

@@ -8,7 +8,10 @@ import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { PageModel } from '~/server/models/page';
 import type { PageModel } from '~/server/models/page';
 import type { IPageRedirect, PageRedirectModel } from '~/server/models/page-redirect';
 import type { IPageRedirect, PageRedirectModel } from '~/server/models/page-redirect';
 
 
-import type { InitialProps, SameRouteEachProps } from '../general-page';
+import type { CommonEachProps } from '../common-props';
+import type { InitialProps } from '../general-page';
+
+import type { EachProps } from './types';
 
 
 // Utility to resolve path, redirect, and identical path page check
 // Utility to resolve path, redirect, and identical path page check
 type PathResolutionResult = {
 type PathResolutionResult = {
@@ -57,7 +60,7 @@ export async function getPageDataForInitial(
     context: GetServerSidePropsContext,
     context: GetServerSidePropsContext,
 ): Promise<GetServerSidePropsResult<
 ): Promise<GetServerSidePropsResult<
   Pick<InitialProps, 'pageWithMeta' | 'isNotFound' | 'isNotCreatable' | 'isForbidden' | 'skipSSR'> &
   Pick<InitialProps, 'pageWithMeta' | 'isNotFound' | 'isNotCreatable' | 'isForbidden' | 'skipSSR'> &
-  Pick<SameRouteEachProps, 'currentPathname' | 'isIdenticalPathPage'>
+  Pick<EachProps, 'currentPathname' | 'isIdenticalPathPage' | 'redirectFrom'>
 >> {
 >> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi, user } = req;
   const { crowi, user } = req;
@@ -74,7 +77,7 @@ export async function getPageDataForInitial(
   const pageId = _isPermalink(pathFromUrl) ? removeHeadingSlash(pathFromUrl) : null;
   const pageId = _isPermalink(pathFromUrl) ? removeHeadingSlash(pathFromUrl) : null;
   const isPermalink = _isPermalink(pathFromUrl);
   const isPermalink = _isPermalink(pathFromUrl);
 
 
-  const { resolvedPathname, isIdenticalPathPage } = await resolvePathAndCheckIdentical(pathFromUrl, user);
+  const { resolvedPathname, isIdenticalPathPage, redirectFrom } = await resolvePathAndCheckIdentical(pathFromUrl, user);
 
 
   if (isIdenticalPathPage) {
   if (isIdenticalPathPage) {
     return {
     return {
@@ -86,6 +89,7 @@ export async function getPageDataForInitial(
         isNotCreatable: true,
         isNotCreatable: true,
         isForbidden: false,
         isForbidden: false,
         skipSSR: false,
         skipSSR: false,
+        redirectFrom,
       },
       },
     };
     };
   }
   }
@@ -133,6 +137,7 @@ export async function getPageDataForInitial(
         isNotCreatable: false,
         isNotCreatable: false,
         isForbidden: false,
         isForbidden: false,
         skipSSR,
         skipSSR,
+        redirectFrom,
       },
       },
     };
     };
   }
   }
@@ -151,6 +156,7 @@ export async function getPageDataForInitial(
       isNotCreatable: !isCreatablePage(resolvedPathname),
       isNotCreatable: !isCreatablePage(resolvedPathname),
       isForbidden: count > 0,
       isForbidden: count > 0,
       skipSSR: false,
       skipSSR: false,
+      redirectFrom,
     },
     },
   };
   };
 }
 }
@@ -158,7 +164,10 @@ export async function getPageDataForInitial(
 // Page data retrieval for same-route navigation
 // Page data retrieval for same-route navigation
 export async function getPageDataForSameRoute(
 export async function getPageDataForSameRoute(
     context: GetServerSidePropsContext,
     context: GetServerSidePropsContext,
-): Promise<GetServerSidePropsResult<Pick<SameRouteEachProps, 'currentPathname' | 'isIdenticalPathPage' | 'redirectFrom'>>> {
+): Promise<GetServerSidePropsResult<
+    Pick<CommonEachProps, 'currentPathname'> &
+    Pick<EachProps, 'currentPathname' | 'isIdenticalPathPage' | 'redirectFrom'>
+>> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const req: CrowiRequest = context.req as CrowiRequest;
   const { user } = req;
   const { user } = req;
 
 

+ 7 - 6
apps/app/src/pages/[[...path]]/server-side-props.ts

@@ -1,18 +1,19 @@
 import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
 import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
 
 
 import {
 import {
-  getServerSideI18nProps, getServerSideUserUISettingsProps, getServerSideCommonInitialProps, getServerSideCommonEachProps,
+  getServerSideI18nProps, getServerSideUserUISettingsProps, getServerSideCommonInitialProps, getServerSideCommonEachProps, isValidCommonEachRouteProps,
 } from '../common-props';
 } from '../common-props';
-import type { InitialProps, SameRouteEachProps } from '../general-page';
+import type { InitialProps } from '../general-page';
 import {
 import {
   getServerSideConfigurationProps, getServerSideRendererConfigProps, getServerSideSidebarConfigProps,
   getServerSideConfigurationProps, getServerSideRendererConfigProps, getServerSideSidebarConfigProps,
-  getActivityAction, isValidInitialAndSameRouteProps, isValidSameRouteProps,
+  getActivityAction, isValidInitialAndSameRouteProps,
 } from '../general-page';
 } from '../general-page';
 import { addActivity } from '../utils/activity';
 import { addActivity } from '../utils/activity';
 import { mergeGetServerSidePropsResults } from '../utils/server-side-props';
 import { mergeGetServerSidePropsResults } from '../utils/server-side-props';
 
 
 import { NEXT_JS_ROUTING_PAGE } from './consts';
 import { NEXT_JS_ROUTING_PAGE } from './consts';
 import { getPageDataForInitial, getPageDataForSameRoute } from './page-data-props';
 import { getPageDataForInitial, getPageDataForSameRoute } from './page-data-props';
+import type { EachProps } from './types';
 
 
 
 
 const nextjsRoutingProps = {
 const nextjsRoutingProps = {
@@ -21,7 +22,7 @@ const nextjsRoutingProps = {
   },
   },
 };
 };
 
 
-export async function getServerSidePropsForInitial(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<InitialProps & SameRouteEachProps>> {
+export async function getServerSidePropsForInitial(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<InitialProps & EachProps>> {
   //
   //
   // STAGE 1
   // STAGE 1
   //
   //
@@ -91,7 +92,7 @@ export async function getServerSidePropsForInitial(context: GetServerSidePropsCo
   return mergedResult;
   return mergedResult;
 }
 }
 
 
-export async function getServerSidePropsForSameRoute(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<SameRouteEachProps>> {
+export async function getServerSidePropsForSameRoute(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<EachProps>> {
   //
   //
   // STAGE 1
   // STAGE 1
   //
   //
@@ -130,7 +131,7 @@ export async function getServerSidePropsForSameRoute(context: GetServerSideProps
   }
   }
 
 
   // Validate the merged props have all required properties
   // Validate the merged props have all required properties
-  if (!isValidSameRouteProps(mergedResult.props)) {
+  if (!isValidCommonEachRouteProps(mergedResult.props)) {
     throw new Error('Invalid same route props structure');
     throw new Error('Invalid same route props structure');
   }
   }
 
 

+ 10 - 0
apps/app/src/pages/[[...path]]/types.ts

@@ -0,0 +1,10 @@
+import type { CommonEachProps } from '../common-props';
+
+export type EachProps = CommonEachProps & {
+  redirectFrom?: string;
+
+  isIdenticalPathPage: boolean,
+
+  templateTagData?: string[],
+  templateBodyData?: string,
+}

+ 0 - 0
apps/app/src/pages/general-page/use-same-route-navigation.spec.tsx → apps/app/src/pages/[[...path]]/use-same-route-navigation.spec.tsx


+ 0 - 0
apps/app/src/pages/general-page/use-same-route-navigation.ts → apps/app/src/pages/[[...path]]/use-same-route-navigation.ts


+ 2 - 2
apps/app/src/pages/[[...path]]/use-shallow-routing.ts

@@ -3,13 +3,13 @@ import { useEffect, useRef } from 'react';
 import { isClient } from '@growi/core/dist/utils';
 import { isClient } from '@growi/core/dist/utils';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
 
 
-import type { Props } from '../general-page';
+import type { CommonEachProps } from '../common-props';
 
 
 /**
 /**
  * Custom hook for syncing pathname by Shallow Routing
  * Custom hook for syncing pathname by Shallow Routing
  * Optimized to minimize unnecessary router operations and re-renders
  * Optimized to minimize unnecessary router operations and re-renders
  */
  */
-export const useShallowRouting = (props: Props): void => {
+export const useShallowRouting = (props: CommonEachProps): void => {
   const router = useRouter();
   const router = useRouter();
   const lastPathnameRef = useRef<string>();
   const lastPathnameRef = useRef<string>();
 
 

+ 26 - 15
apps/app/src/pages/_app.page.tsx

@@ -18,7 +18,6 @@ import { useAutoUpdateGlobalAtoms } from '~/states/global/auto-update';
 import { useHydrateGlobalInitialAtoms } from '~/states/global/hydrate';
 import { useHydrateGlobalInitialAtoms } from '~/states/global/hydrate';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
 
 
-import type { CommonEachProps, CommonInitialProps } from './common-props';
 import { getLocaleAtServerSide } from './utils/locale';
 import { getLocaleAtServerSide } from './utils/locale';
 import { useNextjsRoutingPageRegister } from './utils/nextjs-routing-utils';
 import { useNextjsRoutingPageRegister } from './utils/nextjs-routing-utils';
 import { registerTransformerForObjectId } from './utils/objectid-transformer';
 import { registerTransformerForObjectId } from './utils/objectid-transformer';
@@ -26,6 +25,20 @@ import { registerTransformerForObjectId } from './utils/objectid-transformer';
 import '~/styles/prebuilt/vendor.css';
 import '~/styles/prebuilt/vendor.css';
 import '~/styles/style-app.scss';
 import '~/styles/style-app.scss';
 
 
+// register custom serializer
+registerTransformerForObjectId();
+
+const StateManagementContainer = ({ children }: { children: ReactNode }): JSX.Element => {
+  return (
+    <SWRConfig value={swrGlobalConfiguration}>
+      <Provider>
+        {children}
+      </Provider>
+    </SWRConfig>
+  );
+};
+
+
 // eslint-disable-next-line @typescript-eslint/ban-types
 // eslint-disable-next-line @typescript-eslint/ban-types
 export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
 export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
   getLayout?: (page: JSX.Element) => ReactNode,
   getLayout?: (page: JSX.Element) => ReactNode,
@@ -36,19 +49,14 @@ type GrowiAppProps = AppProps & {
   userLocale: Locale,
   userLocale: Locale,
 };
 };
 
 
-// register custom serializer
-registerTransformerForObjectId();
-
-function GrowiApp({ Component, pageProps, userLocale }: GrowiAppProps): JSX.Element {
+const GrowiAppSubstance = ({ Component, pageProps, userLocale }: GrowiAppProps): JSX.Element => {
   const router = useRouter();
   const router = useRouter();
 
 
-  const commonPageProps = pageProps as CommonInitialProps & CommonEachProps;
-
   // Hydrate global atoms with server-side data
   // Hydrate global atoms with server-side data
-  useHydrateGlobalInitialAtoms(commonPageProps);
-  useAutoUpdateGlobalAtoms(commonPageProps);
+  useHydrateGlobalInitialAtoms(pageProps);
+  useAutoUpdateGlobalAtoms(pageProps);
 
 
-  useNextjsRoutingPageRegister(commonPageProps.nextjsRoutingPage);
+  useNextjsRoutingPageRegister(pageProps.nextjsRoutingPage);
 
 
   useEffect(() => {
   useEffect(() => {
     const updateLangAttribute = () => {
     const updateLangAttribute = () => {
@@ -69,18 +77,21 @@ function GrowiApp({ Component, pageProps, userLocale }: GrowiAppProps): JSX.Elem
   // Use the layout defined at the page level, if available
   // Use the layout defined at the page level, if available
   const getLayout = Component.getLayout ?? (page => page);
   const getLayout = Component.getLayout ?? (page => page);
 
 
+  return <>{getLayout(<Component {...pageProps} />)}</>;
+};
+
+function GrowiApp(props: GrowiAppProps): JSX.Element {
   return (
   return (
     <>
     <>
       <GlobalFonts />
       <GlobalFonts />
-      <SWRConfig value={swrGlobalConfiguration}>
-        <Provider>
-          {getLayout(<Component {...pageProps} />)}
-        </Provider>
-      </SWRConfig>
+      <StateManagementContainer>
+        <GrowiAppSubstance {...props} />
+      </StateManagementContainer>
     </>
     </>
   );
   );
 }
 }
 
 
+// inject userLocale by context
 GrowiApp.getInitialProps = async(appContext: AppContext) => {
 GrowiApp.getInitialProps = async(appContext: AppContext) => {
   const appProps = App.getInitialProps(appContext);
   const appProps = App.getInitialProps(appContext);
   const userLocale = getLocaleAtServerSide(appContext.ctx.req as unknown as CrowiRequest);
   const userLocale = getLocaleAtServerSide(appContext.ctx.req as unknown as CrowiRequest);

+ 40 - 0
apps/app/src/pages/common-props/commons.ts

@@ -3,6 +3,10 @@ import type { GetServerSideProps, GetServerSidePropsContext } from 'next';
 
 
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import { getGrowiVersion } from '~/utils/growi-version';
 import { getGrowiVersion } from '~/utils/growi-version';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:pages:common-props:commons');
+
 
 
 export type CommonInitialProps = {
 export type CommonInitialProps = {
   isNextjsRoutingTypeInitial: true,
   isNextjsRoutingTypeInitial: true,
@@ -51,6 +55,42 @@ export type CommonEachProps = {
   redirectDestination?: string | null,
   redirectDestination?: string | null,
 };
 };
 
 
+/**
+ * Type guard for SameRouteEachProps validation
+ * Lightweight validation for same-route navigation
+ */
+export function isValidCommonEachRouteProps(props: unknown): props is CommonEachProps {
+  if (typeof props !== 'object' || props === null) {
+    logger.warn('isValidCommonEachRouteProps: props is not an object or is null');
+    return false;
+  }
+
+  const p = props as Record<string, unknown>;
+
+  // Essential properties validation
+  if (typeof p.nextjsRoutingPage !== 'string' && p.nextjsRoutingPage !== null) {
+    logger.warn('isValidCommonEachRouteProps: nextjsRoutingPage is not a string or null', { nextjsRoutingPage: p.nextjsRoutingPage });
+    return false;
+  }
+  if (typeof p.currentPathname !== 'string') {
+    logger.warn('isValidCommonEachRouteProps: currentPathname is not a string', { currentPathname: p.currentPathname });
+    return false;
+  }
+  if (typeof p.csrfToken !== 'string') {
+    logger.warn('isValidCommonEachRouteProps: csrfToken is not a string', { csrfToken: p.csrfToken });
+    return false;
+  }
+  if (typeof p.isMaintenanceMode !== 'boolean') {
+    logger.warn('isValidCommonEachRouteProps: isMaintenanceMode is not a boolean', { isMaintenanceMode: p.isMaintenanceMode });
+    return false;
+  }
+  if (typeof p.isIdenticalPathPage !== 'boolean') {
+    logger.warn('isValidCommonEachRouteProps: isIdenticalPathPage is not a boolean', { isIdenticalPathPage: p.isIdenticalPathPage });
+    return false;
+  }
+
+  return true;
+}
 
 
 export const getServerSideCommonEachProps: GetServerSideProps<Omit<CommonEachProps, 'nextjsRoutingPage'>> = async(context: GetServerSidePropsContext) => {
 export const getServerSideCommonEachProps: GetServerSideProps<Omit<CommonEachProps, 'nextjsRoutingPage'>> = async(context: GetServerSidePropsContext) => {
   const req = context.req as CrowiRequest;
   const req = context.req as CrowiRequest;

+ 1 - 1
apps/app/src/pages/common-props/index.ts

@@ -1,6 +1,6 @@
 export {
 export {
   type CommonInitialProps, getServerSideCommonInitialProps,
   type CommonInitialProps, getServerSideCommonInitialProps,
-  type CommonEachProps, getServerSideCommonEachProps,
+  type CommonEachProps, getServerSideCommonEachProps, isValidCommonEachRouteProps,
 } from './commons';
 } from './commons';
 export { getServerSideI18nProps } from './i18n';
 export { getServerSideI18nProps } from './i18n';
 export { getServerSideUserUISettingsProps, type UserUISettingsProps } from './user-ui-settings';
 export { getServerSideUserUISettingsProps, type UserUISettingsProps } from './user-ui-settings';

+ 1 - 2
apps/app/src/pages/general-page/index.ts

@@ -1,6 +1,5 @@
 export { getServerSideSidebarConfigProps, getServerSideRendererConfigProps, getServerSideConfigurationProps } from './configuration-props';
 export { getServerSideSidebarConfigProps, getServerSideRendererConfigProps, getServerSideConfigurationProps } from './configuration-props';
 export { getActivityAction } from './get-activity-action';
 export { getActivityAction } from './get-activity-action';
 export type * from './types';
 export type * from './types';
-export { isValidInitialAndSameRouteProps, isValidSameRouteProps } from './type-guards';
+export { isValidInitialAndSameRouteProps } from './type-guards';
 export { useInitialCSRFetch } from './use-initial-skip-ssr-fetch';
 export { useInitialCSRFetch } from './use-initial-skip-ssr-fetch';
-export { useSameRouteNavigation } from './use-same-route-navigation';

+ 6 - 6
apps/app/src/pages/general-page/superjson/page-to-show-revision-with-meta.ts

@@ -1,4 +1,3 @@
-import { isIPageInfo } from '@growi/core';
 import type {
 import type {
   IDataWithMeta,
   IDataWithMeta,
 } from '@growi/core';
 } from '@growi/core';
@@ -6,7 +5,7 @@ import superjson from 'superjson';
 
 
 import type { IPageToShowRevisionWithMeta } from '../types';
 import type { IPageToShowRevisionWithMeta } from '../types';
 
 
-type IPageToShowRevisionWithMetaSerialized = IDataWithMeta<string, string>;
+type IPageToShowRevisionWithMetaSerialized = IDataWithMeta<string, string | undefined>;
 
 
 let isRegistered = false;
 let isRegistered = false;
 
 
@@ -16,14 +15,15 @@ export const registerPageToShowRevisionWithMeta = (): void => {
   superjson.registerCustom<IPageToShowRevisionWithMeta, IPageToShowRevisionWithMetaSerialized>(
   superjson.registerCustom<IPageToShowRevisionWithMeta, IPageToShowRevisionWithMetaSerialized>(
     {
     {
       isApplicable: (v): v is IPageToShowRevisionWithMeta => {
       isApplicable: (v): v is IPageToShowRevisionWithMeta => {
-        return v?.data != null
-          && v?.data.toObject != null
-          && isIPageInfo(v.meta);
+        const data = v?.data;
+        return data != null
+          && data.toObject != null
+          && data.revision != null && typeof data.revision === 'object';
       },
       },
       serialize: (v) => {
       serialize: (v) => {
         return {
         return {
           data: superjson.stringify(v.data.toObject()),
           data: superjson.stringify(v.data.toObject()),
-          meta: superjson.stringify(v.meta),
+          meta: v.meta != null ? superjson.stringify(v.meta) : undefined,
         };
         };
       },
       },
       deserialize: (v) => {
       deserialize: (v) => {

+ 6 - 46
apps/app/src/pages/general-page/type-guards.ts

@@ -1,53 +1,19 @@
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-import type { InitialProps, SameRouteEachProps } from '.';
+import type { CommonEachProps } from '../common-props';
+import { isValidCommonEachRouteProps } from '../common-props';
 
 
-const logger = loggerFactory('growi:pages:general-page:type-guards');
+import type { InitialProps } from './types';
 
 
-/**
- * Type guard for SameRouteEachProps validation
- * Lightweight validation for same-route navigation
- */
-export function isValidSameRouteProps(props: unknown): props is SameRouteEachProps {
-  if (typeof props !== 'object' || props === null) {
-    logger.warn('isValidSameRouteProps: props is not an object or is null');
-    return false;
-  }
-
-  const p = props as Record<string, unknown>;
-
-  // Essential properties validation
-  if (typeof p.nextjsRoutingPage !== 'string' && p.nextjsRoutingPage !== null) {
-    logger.warn('isValidSameRouteProps: nextjsRoutingPage is not a string or null', { nextjsRoutingPage: p.nextjsRoutingPage });
-    return false;
-  }
-  if (typeof p.currentPathname !== 'string') {
-    logger.warn('isValidSameRouteProps: currentPathname is not a string', { currentPathname: p.currentPathname });
-    return false;
-  }
-  if (typeof p.csrfToken !== 'string') {
-    logger.warn('isValidSameRouteProps: csrfToken is not a string', { csrfToken: p.csrfToken });
-    return false;
-  }
-  if (typeof p.isMaintenanceMode !== 'boolean') {
-    logger.warn('isValidSameRouteProps: isMaintenanceMode is not a boolean', { isMaintenanceMode: p.isMaintenanceMode });
-    return false;
-  }
-  if (typeof p.isIdenticalPathPage !== 'boolean') {
-    logger.warn('isValidSameRouteProps: isIdenticalPathPage is not a boolean', { isIdenticalPathPage: p.isIdenticalPathPage });
-    return false;
-  }
-
-  return true;
-}
+const logger = loggerFactory('growi:pages:general-page:type-guards');
 
 
 /**
 /**
  * Type guard for InitialProps & SameRouteEachProps validation
  * Type guard for InitialProps & SameRouteEachProps validation
  * First validates SameRouteEachProps, then checks InitialProps-specific properties
  * First validates SameRouteEachProps, then checks InitialProps-specific properties
  */
  */
-export function isValidInitialAndSameRouteProps(props: unknown): props is InitialProps & SameRouteEachProps {
+export function isValidInitialAndSameRouteProps(props: unknown): props is InitialProps & CommonEachProps {
   // First, validate SameRouteEachProps
   // First, validate SameRouteEachProps
-  if (!isValidSameRouteProps(props)) {
+  if (!isValidCommonEachRouteProps(props)) {
     logger.warn('isValidInitialAndSameRouteProps: SameRouteEachProps validation failed');
     logger.warn('isValidInitialAndSameRouteProps: SameRouteEachProps validation failed');
     return false;
     return false;
   }
   }
@@ -65,12 +31,6 @@ export function isValidInitialAndSameRouteProps(props: unknown): props is Initia
     return false;
     return false;
   }
   }
 
 
-  // SSRProps
-  if (typeof p.skipSSR !== 'boolean') {
-    logger.warn('isValidInitialAndSameRouteProps: skipSSR is not a boolean', { skipSSR: p.skipSSR });
-    return false;
-  }
-
   // InitialProps specific page state
   // InitialProps specific page state
   if (typeof p.isNotFound !== 'boolean') {
   if (typeof p.isNotFound !== 'boolean') {
     logger.warn('isValidInitialAndSameRouteProps: isNotFound is not a boolean', { isNotFound: p.isNotFound });
     logger.warn('isValidInitialAndSameRouteProps: isNotFound is not a boolean', { isNotFound: p.isNotFound });

+ 2 - 13
apps/app/src/pages/general-page/types.ts

@@ -7,7 +7,7 @@ import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { PageDocument } from '~/server/models/page';
 import type { PageDocument } from '~/server/models/page';
 import type { ServerConfigurationHyderateArgs } from '~/states/server-configurations/hydrate';
 import type { ServerConfigurationHyderateArgs } from '~/states/server-configurations/hydrate';
 
 
-import type { CommonEachProps, CommonInitialProps, UserUISettingsProps } from '../common-props';
+import type { CommonInitialProps } from '../common-props';
 
 
 export type IPageToShowRevisionWithMeta = IDataWithMeta<IPagePopulatedToShowRevision & PageDocument, IPageInfo>;
 export type IPageToShowRevisionWithMeta = IDataWithMeta<IPagePopulatedToShowRevision & PageDocument, IPageInfo>;
 
 
@@ -23,7 +23,7 @@ export type ServerConfigurationProps = {
   serverConfig: ServerConfigurationHyderateArgs,
   serverConfig: ServerConfigurationHyderateArgs,
 }
 }
 
 
-export type InitialProps = CommonInitialProps & UserUISettingsProps & SidebarConfigProps & RendererConfigProps & ServerConfigurationProps & {
+export type InitialProps = CommonInitialProps & RendererConfigProps & ServerConfigurationProps & {
   pageWithMeta: IPageToShowRevisionWithMeta | null,
   pageWithMeta: IPageToShowRevisionWithMeta | null,
   skipSSR?: boolean,
   skipSSR?: boolean,
 
 
@@ -32,14 +32,3 @@ export type InitialProps = CommonInitialProps & UserUISettingsProps & SidebarCon
   isForbidden: boolean,
   isForbidden: boolean,
   isNotCreatable: boolean,
   isNotCreatable: boolean,
 }
 }
-
-export type SameRouteEachProps = CommonEachProps & {
-  redirectFrom?: string;
-
-  isIdenticalPathPage: boolean,
-
-  templateTagData?: string[],
-  templateBodyData?: string,
-}
-
-export type Props = SameRouteEachProps | (InitialProps & SameRouteEachProps);

+ 51 - 87
apps/app/src/pages/login/index.page.tsx

@@ -5,22 +5,19 @@ import type {
   NextPage, GetServerSideProps, GetServerSidePropsContext,
   NextPage, GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
 } from 'next';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 import Head from 'next/head';
 
 
 import { NoLoginLayout } from '~/components/Layout/NoLoginLayout';
 import { NoLoginLayout } from '~/components/Layout/NoLoginLayout';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { IExternalAccountLoginError } from '~/interfaces/errors/external-account-login-error';
 import type { IExternalAccountLoginError } from '~/interfaces/errors/external-account-login-error';
-import { isExternalAccountLoginError } from '~/interfaces/errors/external-account-login-error';
 import { IExternalAuthProviderType } from '~/interfaces/external-auth-provider';
 import { IExternalAuthProviderType } from '~/interfaces/external-auth-provider';
 import type { RegistrationMode } from '~/interfaces/registration-mode';
 import type { RegistrationMode } from '~/interfaces/registration-mode';
-import type { CommonProps } from '~/pages/utils/commons';
-import { getServerSideCommonProps, generateCustomTitle, getNextI18NextConfig } from '~/pages/utils/commons';
-import {
-  useCsrfToken,
-  useCurrentPathname,
-} from '~/stores-universal/context';
+
+import type { CommonEachProps, CommonInitialProps } from '../common-props';
+import { getServerSideCommonEachProps, getServerSideCommonInitialProps, getServerSideI18nProps } from '../common-props';
+import { useCustomTitle } from '../utils/page-title-customization';
+import { mergeGetServerSidePropsResults } from '../utils/server-side-props';
 
 
 import styles from './index.module.scss';
 import styles from './index.module.scss';
 
 
@@ -30,9 +27,8 @@ const { isPermalink, isUserPage, isUsersTopPage } = pagePathUtils;
 const LoginForm = dynamic(() => import('~/client/components/LoginForm').then(mod => mod.LoginForm), { ssr: false });
 const LoginForm = dynamic(() => import('~/client/components/LoginForm').then(mod => mod.LoginForm), { ssr: false });
 
 
 
 
-type Props = CommonProps & {
+type ServerConfigurationProps = {
   registrationMode: RegistrationMode,
   registrationMode: RegistrationMode,
-  pageWithMetaStr: string,
   isMailerSetup: boolean,
   isMailerSetup: boolean,
   enabledExternalAuthType: IExternalAuthProviderType[],
   enabledExternalAuthType: IExternalAuthProviderType[],
   registrationWhitelist: string[],
   registrationWhitelist: string[],
@@ -45,16 +41,12 @@ type Props = CommonProps & {
   minPasswordLength: number,
   minPasswordLength: number,
 };
 };
 
 
+type Props = CommonInitialProps & CommonEachProps & ServerConfigurationProps;
+
 const LoginPage: NextPage<Props> = (props: Props) => {
 const LoginPage: NextPage<Props> = (props: Props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
-  // commons
-  useCsrfToken(props.csrfToken);
-
-  // page
-  useCurrentPathname(props.currentPathname);
-
-  const title = generateCustomTitle(props, t('login.title'));
+  const title = useCustomTitle(t('login.title'));
   const classNames: string[] = ['login-page', styles['login-page']];
   const classNames: string[] = ['login-page', styles['login-page']];
 
 
   return (
   return (
@@ -79,90 +71,62 @@ const LoginPage: NextPage<Props> = (props: Props) => {
   );
   );
 };
 };
 
 
-/**
- * for Server Side Translations
- * @param context
- * @param props
- * @param namespacesRequired
- */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
-  props._nextI18Next = nextI18NextConfig._nextI18Next;
-}
-
-function injectEnabledStrategies(context: GetServerSidePropsContext, props: Props): void {
-  const req: CrowiRequest = context.req as CrowiRequest;
-  const { crowi } = req;
-  const {
-    configManager,
-  } = crowi;
-
-  props.enabledExternalAuthType = [
-    configManager.getConfig('security:passport-google:isEnabled') === true ? IExternalAuthProviderType.google : undefined,
-    configManager.getConfig('security:passport-github:isEnabled') === true ? IExternalAuthProviderType.github : undefined,
-    configManager.getConfig('security:passport-saml:isEnabled') === true ? IExternalAuthProviderType.saml : undefined,
-    configManager.getConfig('security:passport-oidc:isEnabled') === true ? IExternalAuthProviderType.oidc : undefined,
 
 
-  ]
-    .filter((authType): authType is Exclude<typeof authType, undefined> => authType != null);
-}
-
-async function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): Promise<void> {
+export const getServerSideConfigurationProps: GetServerSideProps<ServerConfigurationProps> = async(context: GetServerSidePropsContext) => {
   const req: CrowiRequest = context.req as CrowiRequest;
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { crowi } = req;
   const {
   const {
-    mailService,
-    configManager,
-    passportService,
+    configManager, mailService, passportService,
   } = crowi;
   } = crowi;
 
 
-  props.isPasswordResetEnabled = configManager.getConfig('security:passport-local:isPasswordResetEnabled');
-  props.isMailerSetup = mailService.isMailerSetup;
-  props.isLocalStrategySetup = passportService.isLocalStrategySetup;
-  props.isLdapStrategySetup = passportService.isLdapStrategySetup;
-  props.isLdapSetupFailed = configManager.getConfig('security:passport-ldap:isEnabled') && !props.isLdapStrategySetup;
-  props.registrationWhitelist = configManager.getConfig('security:registrationWhitelist');
-  props.isEmailAuthenticationEnabled = configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled');
-  props.registrationMode = configManager.getConfig('security:registrationMode');
-  props.minPasswordLength = configManager.getConfig('app:minPasswordLength');
-}
+  return {
+    props: {
+      enabledExternalAuthType: [
+        configManager.getConfig('security:passport-google:isEnabled') === true ? IExternalAuthProviderType.google : undefined,
+        configManager.getConfig('security:passport-github:isEnabled') === true ? IExternalAuthProviderType.github : undefined,
+        configManager.getConfig('security:passport-saml:isEnabled') === true ? IExternalAuthProviderType.saml : undefined,
+        configManager.getConfig('security:passport-oidc:isEnabled') === true ? IExternalAuthProviderType.oidc : undefined,
+      ].filter((authType): authType is Exclude<typeof authType, undefined> => authType != null),
+      isPasswordResetEnabled: configManager.getConfig('security:passport-local:isPasswordResetEnabled'),
+      isMailerSetup: mailService.isMailerSetup,
+      isLocalStrategySetup: passportService.isLocalStrategySetup,
+      isLdapStrategySetup: passportService.isLdapStrategySetup,
+      isLdapSetupFailed: configManager.getConfig('security:passport-ldap:isEnabled') && !passportService.isLdapStrategySetup,
+      registrationWhitelist: configManager.getConfig('security:registrationWhitelist'),
+      isEmailAuthenticationEnabled: configManager.getConfig('security:passport-local:isEmailAuthenticationEnabled'),
+      registrationMode: configManager.getConfig('security:registrationMode'),
+      minPasswordLength: configManager.getConfig('app:minPasswordLength'),
+
+    },
+  };
+};
 
 
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const result = await getServerSideCommonProps(context);
-
+  const req: CrowiRequest = context.req as CrowiRequest;
 
 
   // redirect to the page the user was on before moving to the Login Page
   // redirect to the page the user was on before moving to the Login Page
-  if (context.req.headers.referer != null) {
-    const urlBeforeLogin = new URL(context.req.headers.referer);
+  if (req.headers.referer != null) {
+    const urlBeforeLogin = new URL(req.headers.referer);
     if (isPermalink(urlBeforeLogin.pathname) || isUserPage(urlBeforeLogin.pathname) || isUsersTopPage(urlBeforeLogin.pathname)) {
     if (isPermalink(urlBeforeLogin.pathname) || isUserPage(urlBeforeLogin.pathname) || isUsersTopPage(urlBeforeLogin.pathname)) {
-      (context.req as CrowiRequest).session.redirectTo = urlBeforeLogin.href;
-    }
-  }
-
-  // check for presence
-  // see: https://github.com/vercel/next.js/issues/19271#issuecomment-730006862
-  if (!('props' in result)) {
-    throw new Error('invalid getSSP result');
-  }
-
-  const props: Props = result.props as Props;
-
-  const externalAccountLoginError = (context.req as CrowiRequest).session.externalAccountLoginError;
-  if (externalAccountLoginError != null) {
-    delete (context.req as CrowiRequest).session.externalAccountLoginError;
-    const parsedError = JSON.parse(externalAccountLoginError);
-    if (isExternalAccountLoginError(parsedError)) {
-      props.externalAccountLoginError = { ...parsedError as IExternalAccountLoginError };
+      req.session.redirectTo = urlBeforeLogin.href;
     }
     }
   }
   }
 
 
-  injectServerConfigurations(context, props);
-  injectEnabledStrategies(context, props);
-  await injectNextI18NextConfigurations(context, props, ['translation']);
-
-  return {
-    props,
-  };
+  const [
+    commonInitialResult,
+    commonEachResult,
+    serverConfigResult,
+    i18nPropsResult,
+  ] = await Promise.all([
+    getServerSideCommonInitialProps(context),
+    getServerSideCommonEachProps(context),
+    getServerSideConfigurationProps(context),
+    getServerSideI18nProps(context, ['translation']),
+  ]);
+
+  return mergeGetServerSidePropsResults(commonInitialResult,
+    mergeGetServerSidePropsResults(commonEachResult,
+      mergeGetServerSidePropsResults(serverConfigResult, i18nPropsResult)));
 };
 };
 
 
 export default LoginPage;
 export default LoginPage;

+ 1 - 0
apps/app/src/pages/share/[[...path]]/consts/index.ts

@@ -0,0 +1 @@
+export const NEXT_JS_ROUTING_PAGE = 'share/[[...path]]';

+ 92 - 203
apps/app/src/pages/share/[[...path]]/index.page.tsx

@@ -1,129 +1,85 @@
-import React, { useEffect, type JSX } from 'react';
+import type { ReactNode, JSX } from 'react';
+import React, { useEffect } from 'react';
 
 
-import { type IPagePopulatedToShowRevision, getIdForRef } from '@growi/core';
 import type {
 import type {
   GetServerSideProps, GetServerSidePropsContext,
   GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
 } from 'next';
-import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 import Head from 'next/head';
-import superjson from 'superjson';
 
 
 import { ShareLinkLayout } from '~/components/Layout/ShareLinkLayout';
 import { ShareLinkLayout } from '~/components/Layout/ShareLinkLayout';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import { ShareLinkPageView } from '~/components/ShareLinkPageView';
 import { ShareLinkPageView } from '~/components/ShareLinkPageView';
-import type { SupportedActionType } from '~/interfaces/activity';
-import { SupportedAction } from '~/interfaces/activity';
-import type { CrowiRequest } from '~/interfaces/crowi-request';
-import { RegistrationMode } from '~/interfaces/registration-mode';
-import type { RendererConfig } from '~/interfaces/services/renderer';
-import type { IShareLinkHasId } from '~/interfaces/share-link';
-import type { PageDocument, PageModel } from '~/server/models/page';
-import ShareLink from '~/server/models/share-link';
-import { useHydrateSharedPageAtoms } from '~/states/hydrate/page';
-import { useCurrentPageData, useFetchCurrentPage } from '~/states/page';
+import type { CommonEachProps } from '~/pages/common-props';
+import { NextjsRoutingType, detectNextjsRoutingType } from '~/pages/utils/nextjs-routing-utils';
+import { useCustomTitleForPage } from '~/pages/utils/page-title-customization';
+import { useIsSearchPage, useIsSharedUser } from '~/states/context';
 import {
 import {
-  useCurrentUser, useRendererConfig, useIsSearchPage, useCurrentPathname,
-  useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useIsContainerFluid, useIsEnabledMarp,
-  useIsLocalAccountRegistrationEnabled, useShowPageSideAuthors,
-} from '~/stores-universal/context';
+  useCurrentPageData, useCurrentPagePath,
+} from '~/states/page';
+import { useHydratePageAtoms } from '~/states/page/hydrate';
+import { useDisableLinkSharing, useRendererConfig } from '~/states/server-configurations';
+import { useHydrateServerConfigurationAtoms } from '~/states/server-configurations/hydrate';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import type { NextPageWithLayout } from '../../_app.page';
 import type { NextPageWithLayout } from '../../_app.page';
-import type { CommonProps } from '../../common-props';
-import {
-  getServerSideCommonProps, generateCustomTitleForPage, getNextI18NextConfig, skipSSR, addActivity,
-} from '../../common-props';
+import type { InitialProps } from '../../general-page';
+import { useInitialCSRFetch } from '../../general-page';
+import { registerPageToShowRevisionWithMeta } from '../../general-page/superjson';
 
 
+import { NEXT_JS_ROUTING_PAGE } from './consts';
+import { getServerSidePropsForInitial, getServerSidePropsForSameRoute } from './server-side-props';
+import type { ShareLinkInitialProps } from './types';
 
 
-const GrowiContextualSubNavigationSubstance = dynamic(() => import('~/client/components/Navbar/GrowiContextualSubNavigation'), { ssr: false });
+// call superjson custom register
+registerPageToShowRevisionWithMeta();
 
 
 
 
-const logger = loggerFactory('growi:next-page:share');
+const GrowiContextualSubNavigation = dynamic(() => import('~/client/components/Navbar/GrowiContextualSubNavigation'), { ssr: false });
 
 
-type Props = CommonProps & {
-  shareLinkRelatedPage?: IShareLinkRelatedPage,
-  shareLink?: IShareLinkHasId,
-  isNotFound: boolean,
-  isExpired: boolean,
-  disableLinkSharing: boolean,
-  isSearchServiceConfigured: boolean,
-  isSearchServiceReachable: boolean,
-  isSearchScopeChildrenAsDefault: boolean,
-  showPageSideAuthors: boolean,
-  isEnabledMarp: boolean,
-  isLocalAccountRegistrationEnabled: boolean,
-  drawioUri: string | null,
-  rendererConfig: RendererConfig,
-  skipSSR: boolean,
-  ssrMaxRevisionBodyLength: number,
-};
 
 
-type IShareLinkRelatedPage = IPagePopulatedToShowRevision & PageDocument;
-
-superjson.registerCustom<IShareLinkRelatedPage, string>(
-  {
-    isApplicable: (v): v is IShareLinkRelatedPage => {
-      return v != null
-        && v.toObject != null;
-    },
-    serialize: (v) => { return superjson.stringify(v.toObject()) },
-    deserialize: (v) => { return superjson.parse(v) },
-  },
-  'IShareLinkRelatedPageTransformer',
-);
-
-// GrowiContextualSubNavigation for shared page
-// get page info from props not to send request 'GET /page' from client
-type GrowiContextualSubNavigationForSharedPageProps = {
-  page?: IPagePopulatedToShowRevision,
-  isLinkSharingDisabled: boolean,
-}
+const logger = loggerFactory('growi:next-page:share');
 
 
-const GrowiContextualSubNavigationForSharedPage = (props: GrowiContextualSubNavigationForSharedPageProps): JSX.Element => {
-  const { page, isLinkSharingDisabled } = props;
+type Props = CommonEachProps | (CommonEachProps & InitialProps & ShareLinkInitialProps);
 
 
-  return (
-    <GrowiContextualSubNavigationSubstance currentPage={page} isLinkSharingDisabled={isLinkSharingDisabled} />
-  );
+const isInitialProps = (props: Props): props is (ShareLinkInitialProps & InitialProps & CommonEachProps) => {
+  return 'isNextjsRoutingTypeInitial' in props && props.isNextjsRoutingTypeInitial;
 };
 };
 
 
 const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
-  useHydrateSharedPageAtoms({
-    pageId: props.shareLinkRelatedPage?._id,
-    isNotFound: props.isNotFound,
+
+  // Initialize Jotai atoms with initial data - must be called unconditionally
+  const shareLink = isInitialProps(props) ? props.shareLink : undefined;
+  const isExpired = isInitialProps(props) ? props.isExpired : undefined;
+  const pageData = isInitialProps(props) ? props.pageWithMeta?.data : undefined;
+  useHydratePageAtoms(pageData, {
+    shareLinkId: shareLink?._id,
   });
   });
 
 
   const [currentPage] = useCurrentPageData();
   const [currentPage] = useCurrentPageData();
-  useCurrentPathname(props.shareLink?.relatedPage.path);
-  useIsSearchPage(false);
-  useShareLinkId(props.shareLink?._id);
-  useCurrentUser(props.currentUser);
-  useRendererConfig(props.rendererConfig);
-  useIsSearchServiceConfigured(props.isSearchServiceConfigured);
-  useIsSearchServiceReachable(props.isSearchServiceReachable);
-  useIsSearchScopeChildrenAsDefault(props.isSearchScopeChildrenAsDefault);
-  useIsEnabledMarp(props.rendererConfig.isEnabledMarp);
-  useIsLocalAccountRegistrationEnabled(props.isLocalAccountRegistrationEnabled);
-  useShowPageSideAuthors(props.showPageSideAuthors);
-  useIsContainerFluid(props.isContainerFluid);
-
-  const { fetchCurrentPage } = useFetchCurrentPage();
+  const [currentPagePath] = useCurrentPagePath();
+  const [rendererConfig] = useRendererConfig();
+  const [, setIsSharedUser] = useIsSharedUser();
+  const [, setIsSearchPage] = useIsSearchPage();
+  const [isLinkSharingDisabled] = useDisableLinkSharing();
 
 
-  useEffect(() => {
-    if (!props.skipSSR) {
-      return;
-    }
+  // Use custom hooks for navigation and routing
+  // useSameRouteNavigation();
 
 
-    if (props.shareLink?.relatedPage._id != null && !props.isNotFound) {
-      fetchCurrentPage();
-    }
-  }, [fetchCurrentPage, props.isNotFound, props.shareLink?.relatedPage._id, props.skipSSR]);
+  // If initial props and skipSSR, fetch page data on client-side
+  useInitialCSRFetch(isInitialProps(props) && props.skipSSR);
 
 
+  // Initialize atom values
+  useEffect(() => {
+    setIsSharedUser(true);
+    setIsSearchPage(false);
+  }, [setIsSharedUser, setIsSearchPage]);
 
 
-  const pagePath = props.shareLinkRelatedPage?.path ?? '';
+  // If the data on the page changes without router.push, pageWithMeta remains old because getServerSideProps() is not executed
+  // So preferentially take page data from useSWRxCurrentPage
+  const pagePath = currentPagePath ?? props.currentPathname;
 
 
-  const title = generateCustomTitleForPage(props, pagePath);
+  const title = useCustomTitleForPage(pagePath);
 
 
   return (
   return (
     <>
     <>
@@ -133,15 +89,14 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 
 
       <div className="dynamic-layout-root justify-content-between">
       <div className="dynamic-layout-root justify-content-between">
 
 
-        <GrowiContextualSubNavigationForSharedPage page={currentPage ?? props.shareLinkRelatedPage} isLinkSharingDisabled={props.disableLinkSharing} />
+        <GrowiContextualSubNavigation currentPage={currentPage} />
 
 
         <ShareLinkPageView
         <ShareLinkPageView
           pagePath={pagePath}
           pagePath={pagePath}
-          rendererConfig={props.rendererConfig}
-          page={currentPage ?? props.shareLinkRelatedPage}
-          shareLink={props.shareLink}
-          isExpired={props.isExpired}
-          disableLinkSharing={props.disableLinkSharing}
+          rendererConfig={rendererConfig}
+          shareLink={shareLink}
+          isExpired={isExpired}
+          disableLinkSharing={isLinkSharingDisabled}
         />
         />
 
 
       </div>
       </div>
@@ -149,120 +104,54 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   );
   );
 };
 };
 
 
+type LayoutProps = Props & {
+  children?: ReactNode
+}
+
+const Layout = ({ children, ...props }: LayoutProps): JSX.Element => {
+  // Hydrate sidebar atoms with server-side data - must be called unconditionally
+  const initialProps = isInitialProps(props) ? props : undefined;
+  useHydrateServerConfigurationAtoms(initialProps?.serverConfig, initialProps?.rendererConfig);
+
+  return <ShareLinkLayout>{children}</ShareLinkLayout>;
+};
+
 SharedPage.getLayout = function getLayout(page) {
 SharedPage.getLayout = function getLayout(page) {
   return (
   return (
     <>
     <>
       <DrawioViewerScript drawioUri={page.props.rendererConfig.drawioUri} />
       <DrawioViewerScript drawioUri={page.props.rendererConfig.drawioUri} />
-      <ShareLinkLayout>{page}</ShareLinkLayout>
+      <Layout {...page.props}>
+        {page}
+      </Layout>
     </>
     </>
   );
   );
 };
 };
 
 
-function injectServerConfigurations(context: GetServerSidePropsContext, props: Props): void {
-  const req: CrowiRequest = context.req as CrowiRequest;
-  const { crowi } = req;
-  const { configManager, searchService } = crowi;
-
-  props.disableLinkSharing = configManager.getConfig('security:disableLinkSharing');
-
-  props.isSearchServiceConfigured = searchService.isConfigured;
-  props.isSearchServiceReachable = searchService.isReachable;
-  props.isSearchScopeChildrenAsDefault = configManager.getConfig('customize:isSearchScopeChildrenAsDefault');
-
-  props.drawioUri = configManager.getConfig('app:drawioUri');
-
-  props.showPageSideAuthors = configManager.getConfig('customize:showPageSideAuthors');
-
-  props.isLocalAccountRegistrationEnabled = crowi.passportService.isLocalStrategySetup
-    && configManager.getConfig('security:registrationMode') !== RegistrationMode.CLOSED;
-
-  props.rendererConfig = {
-    isSharedPage: true,
-    isEnabledLinebreaks: configManager.getConfig('markdown:isEnabledLinebreaks'),
-    isEnabledLinebreaksInComments: configManager.getConfig('markdown:isEnabledLinebreaksInComments'),
-    isEnabledMarp: configManager.getConfig('customize:isEnabledMarp'),
-    adminPreferredIndentSize: configManager.getConfig('markdown:adminPreferredIndentSize'),
-    isIndentSizeForced: configManager.getConfig('markdown:isIndentSizeForced'),
-
-    drawioUri: configManager.getConfig('app:drawioUri'),
-    plantumlUri: configManager.getConfig('app:plantumlUri'),
-
-    // XSS Options
-    isEnabledXssPrevention: configManager.getConfig('markdown:rehypeSanitize:isEnabledPrevention'),
-    sanitizeType: configManager.getConfig('markdown:rehypeSanitize:option'),
-    customTagWhitelist: crowi.configManager.getConfig('markdown:rehypeSanitize:tagNames'),
-    customAttrWhitelist: configManager.getConfig('markdown:rehypeSanitize:attributes') != null
-      ? JSON.parse(configManager.getConfig('markdown:rehypeSanitize:attributes'))
-      : undefined,
-    highlightJsStyleBorder: crowi.configManager.getConfig('customize:highlightJsStyleBorder'),
-  };
-
-  props.ssrMaxRevisionBodyLength = configManager.getConfig('app:ssrMaxRevisionBodyLength');
-}
-
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
-  props._nextI18Next = nextI18NextConfig._nextI18Next;
-}
-
-function getAction(props: Props): SupportedActionType {
-  let action: SupportedActionType;
-  if (props.isExpired) {
-    action = SupportedAction.ACTION_SHARE_LINK_EXPIRED_PAGE_VIEW;
+// function getAction(props: Props): SupportedActionType {
+//   let action: SupportedActionType;
+//   if (props.isExpired) {
+//     action = SupportedAction.ACTION_SHARE_LINK_EXPIRED_PAGE_VIEW;
+//   }
+//   else if (props.shareLink == null) {
+//     action = SupportedAction.ACTION_SHARE_LINK_NOT_FOUND;
+//   }
+//   else {
+//     action = SupportedAction.ACTION_SHARE_LINK_PAGE_VIEW;
+//   }
+
+//   return action;
+// }
+
+export const getServerSideProps: GetServerSideProps<Props> = async(context: GetServerSidePropsContext) => {
+  // detect Next.js routing type
+  const nextjsRoutingType = detectNextjsRoutingType(context, NEXT_JS_ROUTING_PAGE);
+
+  if (nextjsRoutingType === NextjsRoutingType.INITIAL) {
+    return getServerSidePropsForInitial(context);
   }
   }
-  else if (props.shareLink == null) {
-    action = SupportedAction.ACTION_SHARE_LINK_NOT_FOUND;
-  }
-  else {
-    action = SupportedAction.ACTION_SHARE_LINK_PAGE_VIEW;
-  }
-
-  return action;
-}
-export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
-  const req = context.req as CrowiRequest;
-  const { crowi, params } = req;
-  const result = await getServerSideCommonProps(context);
-
-  if (!('props' in result)) {
-    throw new Error('invalid getSSP result');
-  }
-  const props: Props = result.props as Props;
-
-  try {
-    const shareLink = await ShareLink.findOne({ _id: params.linkId }).populate('relatedPage');
-    if (shareLink == null) {
-      props.isNotFound = true;
-    }
-    else {
-      props.isNotFound = false;
-      props.isExpired = shareLink.isExpired();
-      props.shareLink = shareLink.toObject();
-
-      // retrieve Page
-      const Page = crowi.model('Page') as PageModel;
-      const relatedPage = await Page.findOne({ _id: getIdForRef(shareLink.relatedPage) });
-      // determine whether skip SSR
-      const ssrMaxRevisionBodyLength = crowi.configManager.getConfig('app:ssrMaxRevisionBodyLength');
-
-      if (relatedPage != null) {
-        props.skipSSR = await skipSSR(relatedPage, ssrMaxRevisionBodyLength);
-        // populate
-        props.shareLinkRelatedPage = await relatedPage.populateDataToShowRevision(props.skipSSR); // shouldExcludeBody = skipSSR
-      }
-    }
-  }
-  catch (err) {
-    logger.error(err);
-  }
-
-  injectServerConfigurations(context, props);
-  await injectNextI18NextConfigurations(context, props);
-  await addActivity(context, getAction(props));
 
 
-  return {
-    props,
-  };
+  // Lightweight props for same-route navigation
+  return getServerSidePropsForSameRoute(context);
 };
 };
 
 
 export default SharedPage;
 export default SharedPage;

+ 93 - 0
apps/app/src/pages/share/[[...path]]/page-data-props.ts

@@ -0,0 +1,93 @@
+import { getIdForRef } from '@growi/core';
+import type { IPage } from '@growi/core';
+import type { model } from 'mongoose';
+import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
+
+import type { CrowiRequest } from '~/interfaces/crowi-request';
+import type { IShareLink } from '~/interfaces/share-link';
+import type { PageModel } from '~/server/models/page';
+import type { ShareLinkModel } from '~/server/models/share-link';
+
+import type { ShareLinkInitialProps } from './types';
+
+
+let mongooseModel: typeof model;
+let Page: PageModel;
+let ShareLink: ShareLinkModel;
+
+export const getPageDataForInitial = async(context: GetServerSidePropsContext):
+    Promise<GetServerSidePropsResult<ShareLinkInitialProps>> => {
+
+  const req = context.req as CrowiRequest;
+  const { crowi, params } = req;
+
+  if (mongooseModel == null) {
+    mongooseModel = (await import('mongoose')).model;
+  }
+  if (Page == null) {
+    Page = mongooseModel<IPage, PageModel>('Page');
+  }
+  if (ShareLink == null) {
+    ShareLink = mongooseModel<IShareLink, ShareLinkModel>('ShareLink');
+  }
+
+  const shareLink = await ShareLink.findOne({ _id: params.linkId }).populate('relatedPage');
+
+  // not found
+  if (shareLink == null) {
+    return {
+      props: {
+        isNotFound: true,
+        pageWithMeta: null,
+        isExpired: undefined,
+        shareLink: undefined,
+      },
+    };
+  }
+
+  // expired
+  if (shareLink.isExpired()) {
+    return {
+      props: {
+        isNotFound: false,
+        pageWithMeta: null,
+        isExpired: true,
+        shareLink,
+      },
+    };
+  }
+
+  // retrieve Page
+  const relatedPage = await Page.findOne({ _id: getIdForRef(shareLink.relatedPage) });
+
+  // not found
+  if (relatedPage == null) {
+    return {
+      props: {
+        isNotFound: true,
+        pageWithMeta: null,
+        isExpired: undefined,
+        shareLink: undefined,
+      },
+    };
+  }
+
+  // Handle existing page
+  const ssrMaxRevisionBodyLength = crowi.configManager.getConfig('app:ssrMaxRevisionBodyLength');
+
+  // Check if SSR should be skipped
+  const latestRevisionBodyLength = await relatedPage.getLatestRevisionBodyLength();
+  const skipSSR = latestRevisionBodyLength != null && ssrMaxRevisionBodyLength < latestRevisionBodyLength;
+
+  const populatedPage = await relatedPage.populateDataToShowRevision(skipSSR);
+
+  return {
+    props: {
+      isNotFound: false,
+      pageWithMeta: { data: populatedPage },
+      skipSSR,
+      isExpired: false,
+      shareLink: shareLink.toObject(),
+    },
+  };
+};

+ 141 - 0
apps/app/src/pages/share/[[...path]]/server-side-props.ts

@@ -0,0 +1,141 @@
+import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
+
+import type { CommonEachProps } from '../../common-props';
+import {
+  getServerSideI18nProps, getServerSideCommonInitialProps, getServerSideCommonEachProps,
+} from '../../common-props';
+import type { InitialProps } from '../../general-page';
+import {
+  getServerSideConfigurationProps, getServerSideRendererConfigProps,
+  getActivityAction, isValidInitialAndSameRouteProps,
+} from '../../general-page';
+import { addActivity } from '../../utils/activity';
+import { mergeGetServerSidePropsResults } from '../../utils/server-side-props';
+
+import { NEXT_JS_ROUTING_PAGE } from './consts';
+import { getPageDataForInitial } from './page-data-props';
+import type { ShareLinkInitialProps } from './types';
+
+
+const nextjsRoutingProps = {
+  props: {
+    nextjsRoutingPage: NEXT_JS_ROUTING_PAGE,
+  },
+};
+
+const basisProps = {
+  props: {
+    isNotCreatable: true,
+    isForbidden: false,
+    isIdenticalPathPage: false,
+  },
+};
+
+export async function getServerSidePropsForInitial(context: GetServerSidePropsContext):
+    Promise<GetServerSidePropsResult<InitialProps & ShareLinkInitialProps & CommonEachProps>> {
+
+  //
+  // STAGE 1
+  //
+
+  const commonEachPropsResult = await getServerSideCommonEachProps(context);
+  // Handle early return cases (redirect/notFound)
+  if ('redirect' in commonEachPropsResult || 'notFound' in commonEachPropsResult) {
+    return commonEachPropsResult;
+  }
+  const commonEachProps = await commonEachPropsResult.props;
+
+  // Handle redirect destination from common props
+  if (commonEachProps.redirectDestination != null) {
+    return {
+      redirect: {
+        permanent: false,
+        destination: commonEachProps.redirectDestination,
+      },
+    };
+  }
+
+  //
+  // STAGE 2
+  //
+
+  const [
+    commonInitialResult,
+    serverConfigResult,
+    rendererConfigResult,
+    i18nPropsResult,
+    pageDataResult,
+  ] = await Promise.all([
+    getServerSideCommonInitialProps(context),
+    getServerSideConfigurationProps(context),
+    getServerSideRendererConfigProps(context),
+    getServerSideI18nProps(context, ['translation']),
+    getPageDataForInitial(context),
+  ]);
+
+  // Merge all results in a type-safe manner (using sequential merging)
+  const mergedResult = mergeGetServerSidePropsResults(commonEachPropsResult,
+    mergeGetServerSidePropsResults(commonInitialResult,
+      mergeGetServerSidePropsResults(serverConfigResult,
+        mergeGetServerSidePropsResults(rendererConfigResult,
+          mergeGetServerSidePropsResults(i18nPropsResult,
+            mergeGetServerSidePropsResults(pageDataResult,
+              mergeGetServerSidePropsResults(nextjsRoutingProps, basisProps)))))));
+
+  // Check for early return (redirect/notFound)
+  if ('redirect' in mergedResult || 'notFound' in mergedResult) {
+    return mergedResult;
+  }
+
+  const mergedProps = await mergedResult.props;
+
+  // Type-safe props validation AFTER skipSSR is properly set
+  if (!isValidInitialAndSameRouteProps(mergedProps)) {
+    throw new Error('Invalid merged props structure');
+  }
+
+  await addActivity(context, getActivityAction(mergedProps));
+  return mergedResult;
+}
+
+export async function getServerSidePropsForSameRoute(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<CommonEachProps>> {
+  //
+  // STAGE 1
+  //
+
+  const commonEachPropsResult = await getServerSideCommonEachProps(context);
+  // Handle early return cases (redirect/notFound)
+  if ('redirect' in commonEachPropsResult || 'notFound' in commonEachPropsResult) {
+    return commonEachPropsResult;
+  }
+  const commonEachProps = await commonEachPropsResult.props;
+
+  // Handle redirect destination from common props
+  if (commonEachProps.redirectDestination != null) {
+    return {
+      redirect: {
+        permanent: false,
+        destination: commonEachProps.redirectDestination,
+      },
+    };
+  }
+
+  //
+  // STAGE 2
+  //
+
+  // Merge results in a type-safe manner
+  const mergedResult = mergeGetServerSidePropsResults(commonEachPropsResult, nextjsRoutingProps);
+
+  // -- TODO: persist activity
+
+  // const mergedProps = await mergedResult.props;
+
+  // // Type-safe props validation AFTER skipSSR is properly set
+  // if (!isValidSameRouteProps(mergedProps)) {
+  //   throw new Error('Invalid same route props structure');
+  // }
+
+  // await addActivity(context, getActivityAction(mergedProps));
+  return mergedResult;
+}

+ 18 - 0
apps/app/src/pages/share/[[...path]]/types.ts

@@ -0,0 +1,18 @@
+import type { IShareLinkHasId } from '~/interfaces/share-link';
+import type { InitialProps } from '~/pages/general-page';
+
+export type ShareLinkInitialProps = Pick<InitialProps, 'pageWithMeta' | 'skipSSR'> & (
+  {
+    isNotFound: true,
+    isExpired: undefined,
+    shareLink: undefined,
+  } | {
+    isNotFound: false,
+    isExpired: true,
+    shareLink: IShareLinkHasId,
+  } | {
+    isNotFound: false,
+    isExpired: false,
+    shareLink: IShareLinkHasId,
+  }
+);

+ 8 - 8
apps/app/src/states/global/hydrate.ts

@@ -1,16 +1,14 @@
 import { useHydrateAtoms } from 'jotai/utils';
 import { useHydrateAtoms } from 'jotai/utils';
-
-import type { CommonInitialProps } from '~/pages/utils/commons';
-
+import type { CommonInitialProps } from '~/pages/common-props';
 import {
 import {
   appTitleAtom,
   appTitleAtom,
-  siteUrlAtom,
   confidentialAtom,
   confidentialAtom,
-  growiVersionAtom,
-  isDefaultLogoAtom,
   customTitleTemplateAtom,
   customTitleTemplateAtom,
-  growiCloudUriAtom,
   forcedColorSchemeAtom,
   forcedColorSchemeAtom,
+  growiCloudUriAtom,
+  growiVersionAtom,
+  isDefaultLogoAtom,
+  siteUrlAtom,
 } from './global';
 } from './global';
 
 
 /**
 /**
@@ -19,7 +17,9 @@ import {
  *
  *
  * @param commonInitialProps - Server-side common properties from getServerSideCommonInitialProps
  * @param commonInitialProps - Server-side common properties from getServerSideCommonInitialProps
  */
  */
-export const useHydrateGlobalInitialAtoms = (commonInitialProps: CommonInitialProps): void => {
+export const useHydrateGlobalInitialAtoms = (
+  commonInitialProps: CommonInitialProps,
+): void => {
   // Hydrate global atoms with server-side data
   // Hydrate global atoms with server-side data
   useHydrateAtoms([
   useHydrateAtoms([
     [appTitleAtom, commonInitialProps.appTitle],
     [appTitleAtom, commonInitialProps.appTitle],

+ 22 - 12
apps/app/src/states/page/hooks.ts

@@ -5,21 +5,21 @@ import { useCurrentPathname } from '../global';
 import type { UseAtom } from '../helper';
 import type { UseAtom } from '../helper';
 
 
 import {
 import {
-  currentPageIdAtom,
   currentPageDataAtom,
   currentPageDataAtom,
+  currentPageIdAtom,
   currentPagePathAtom,
   currentPagePathAtom,
-  pageNotFoundAtom,
-  pageNotCreatableAtom,
+  isRevisionOutdatedAtom,
+  isTrashPageAtom,
   latestRevisionAtom,
   latestRevisionAtom,
-  // New atoms for enhanced functionality
-  remoteRevisionIdAtom,
+  pageNotCreatableAtom,
+  pageNotFoundAtom,
   remoteRevisionBodyAtom,
   remoteRevisionBodyAtom,
-  remoteRevisionLastUpdateUserAtom,
+  remoteRevisionIdAtom,
   remoteRevisionLastUpdatedAtAtom,
   remoteRevisionLastUpdatedAtAtom,
-  isTrashPageAtom,
-  isRevisionOutdatedAtom,
-  templateTagsAtom,
+  remoteRevisionLastUpdateUserAtom,
+  shareLinkIdAtom,
   templateBodyAtom,
   templateBodyAtom,
+  templateTagsAtom,
 } from './internal-atoms';
 } from './internal-atoms';
 
 
 /**
 /**
@@ -48,6 +48,10 @@ export const useLatestRevision = (): UseAtom<typeof latestRevisionAtom> => {
   return useAtom(latestRevisionAtom);
   return useAtom(latestRevisionAtom);
 };
 };
 
 
+export const useShareLinkId = (): UseAtom<typeof shareLinkIdAtom> => {
+  return useAtom(shareLinkIdAtom);
+};
+
 export const useTemplateTags = (): UseAtom<typeof templateTagsAtom> => {
 export const useTemplateTags = (): UseAtom<typeof templateTagsAtom> => {
   return useAtom(templateTagsAtom);
   return useAtom(templateTagsAtom);
 };
 };
@@ -61,15 +65,21 @@ export const useRemoteRevisionId = (): UseAtom<typeof remoteRevisionIdAtom> => {
   return useAtom(remoteRevisionIdAtom);
   return useAtom(remoteRevisionIdAtom);
 };
 };
 
 
-export const useRemoteRevisionBody = (): UseAtom<typeof remoteRevisionBodyAtom> => {
+export const useRemoteRevisionBody = (): UseAtom<
+  typeof remoteRevisionBodyAtom
+> => {
   return useAtom(remoteRevisionBodyAtom);
   return useAtom(remoteRevisionBodyAtom);
 };
 };
 
 
-export const useRemoteRevisionLastUpdateUser = (): UseAtom<typeof remoteRevisionLastUpdateUserAtom> => {
+export const useRemoteRevisionLastUpdateUser = (): UseAtom<
+  typeof remoteRevisionLastUpdateUserAtom
+> => {
   return useAtom(remoteRevisionLastUpdateUserAtom);
   return useAtom(remoteRevisionLastUpdateUserAtom);
 };
 };
 
 
-export const useRemoteRevisionLastUpdatedAt = (): UseAtom<typeof remoteRevisionLastUpdatedAtAtom> => {
+export const useRemoteRevisionLastUpdatedAt = (): UseAtom<
+  typeof remoteRevisionLastUpdatedAtAtom
+> => {
   return useAtom(remoteRevisionLastUpdatedAtAtom);
   return useAtom(remoteRevisionLastUpdatedAtAtom);
 };
 };
 
 

+ 19 - 43
apps/app/src/states/page/hydrate.ts

@@ -2,15 +2,16 @@ import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { useHydrateAtoms } from 'jotai/utils';
 import { useHydrateAtoms } from 'jotai/utils';
 
 
 import {
 import {
-  currentPageIdAtom,
   currentPageDataAtom,
   currentPageDataAtom,
-  pageNotFoundAtom,
+  currentPageIdAtom,
   latestRevisionAtom,
   latestRevisionAtom,
-  templateTagsAtom,
-  templateBodyAtom,
-  remoteRevisionIdAtom,
-  remoteRevisionBodyAtom,
   pageNotCreatableAtom,
   pageNotCreatableAtom,
+  pageNotFoundAtom,
+  remoteRevisionBodyAtom,
+  remoteRevisionIdAtom,
+  shareLinkIdAtom,
+  templateBodyAtom,
+  templateTagsAtom,
 } from './internal-atoms';
 } from './internal-atoms';
 
 
 /**
 /**
@@ -37,14 +38,15 @@ import {
  * });
  * });
  */
  */
 export const useHydratePageAtoms = (
 export const useHydratePageAtoms = (
-    page: IPagePopulatedToShowRevision | undefined,
-    options?: {
-      isNotFound?: boolean;
-      isNotCreatable?: boolean;
-      isLatestRevision?: boolean;
-      templateTags?: string[];
-      templateBody?: string;
-    },
+  page: IPagePopulatedToShowRevision | undefined,
+  options?: {
+    isNotFound?: boolean;
+    isNotCreatable?: boolean;
+    isLatestRevision?: boolean;
+    shareLinkId?: string;
+    templateTags?: string[];
+    templateBody?: string;
+  },
 ): void => {
 ): void => {
   useHydrateAtoms([
   useHydrateAtoms([
     // Core page state - automatically extract from page object
     // Core page state - automatically extract from page object
@@ -54,6 +56,9 @@ export const useHydratePageAtoms = (
     [pageNotCreatableAtom, options?.isNotCreatable ?? false],
     [pageNotCreatableAtom, options?.isNotCreatable ?? false],
     [latestRevisionAtom, options?.isLatestRevision ?? true],
     [latestRevisionAtom, options?.isLatestRevision ?? true],
 
 
+    // ShareLink page state
+    [shareLinkIdAtom, options?.shareLinkId],
+
     // Template data - from options (not auto-extracted from page)
     // Template data - from options (not auto-extracted from page)
     [templateTagsAtom, options?.templateTags ?? []],
     [templateTagsAtom, options?.templateTags ?? []],
     [templateBodyAtom, options?.templateBody ?? ''],
     [templateBodyAtom, options?.templateBody ?? ''],
@@ -63,32 +68,3 @@ export const useHydratePageAtoms = (
     [remoteRevisionBodyAtom, page?.revision?.body],
     [remoteRevisionBodyAtom, page?.revision?.body],
   ]);
   ]);
 };
 };
-
-/**
- * Hook for hydrating shared page atoms with server-side data
- * This is a simplified version that focuses on the most common use case:
- * hydrating with page ID and not found status
- * @param args
- */
-export const useHydrateSharedPageAtoms = (
-    args: {
-      pageId: string | undefined;
-      isNotFound: boolean;
-    },
-): void => {
-  useHydrateAtoms([
-    // Core page state - automatically extract from page object
-    [currentPageIdAtom, args.pageId],
-    [currentPageDataAtom, undefined],
-    [pageNotFoundAtom, args.isNotFound],
-    [latestRevisionAtom, true],
-
-    // Template data - from options (not auto-extracted from page)
-    [templateTagsAtom, []],
-    [templateBodyAtom, ''],
-
-    // Remote revision data - auto-extracted from page.revision
-    [remoteRevisionIdAtom, undefined],
-    [remoteRevisionBodyAtom, undefined],
-  ]);
-};

+ 13 - 6
apps/app/src/states/page/internal-atoms.ts

@@ -14,6 +14,9 @@ export const pageNotFoundAtom = atom(false);
 export const pageNotCreatableAtom = atom(false);
 export const pageNotCreatableAtom = atom(false);
 export const latestRevisionAtom = atom(true);
 export const latestRevisionAtom = atom(true);
 
 
+// ShareLink page state atoms (internal)
+export const shareLinkIdAtom = atom<string>();
+
 // Fetch state atoms (internal)
 // Fetch state atoms (internal)
 export const pageLoadingAtom = atom(false);
 export const pageLoadingAtom = atom(false);
 export const pageErrorAtom = atom<Error | null>(null);
 export const pageErrorAtom = atom<Error | null>(null);
@@ -84,12 +87,16 @@ export const setTemplateContentAtom = atom(
 
 
 export const setRemoteRevisionDataAtom = atom(
 export const setRemoteRevisionDataAtom = atom(
   null,
   null,
-  (get, set, data: {
-    id?: string;
-    body?: string;
-    lastUpdateUser?: IUserHasId;
-    lastUpdatedAt?: Date;
-  }) => {
+  (
+    get,
+    set,
+    data: {
+      id?: string;
+      body?: string;
+      lastUpdateUser?: IUserHasId;
+      lastUpdatedAt?: Date;
+    },
+  ) => {
     if (data.id !== undefined) {
     if (data.id !== undefined) {
       set(remoteRevisionIdAtom, data.id);
       set(remoteRevisionIdAtom, data.id);
     }
     }

+ 126 - 105
apps/app/src/states/page/use-fetch-current-page.ts

@@ -1,142 +1,163 @@
-import { useCallback } from 'react';
-
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { isClient } from '@growi/core/dist/utils';
 import { isClient } from '@growi/core/dist/utils';
-import { isCreatablePage, isPermalink } from '@growi/core/dist/utils/page-path-utils';
+import {
+  isCreatablePage,
+  isPermalink,
+} from '@growi/core/dist/utils/page-path-utils';
 import { removeHeadingSlash } from '@growi/core/dist/utils/path-utils';
 import { removeHeadingSlash } from '@growi/core/dist/utils/path-utils';
 import { useAtomValue } from 'jotai';
 import { useAtomValue } from 'jotai';
 import { useAtomCallback } from 'jotai/utils';
 import { useAtomCallback } from 'jotai/utils';
+import { useCallback } from 'react';
 
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { apiv3Get } from '~/client/util/apiv3-client';
-import { useShareLinkId } from '~/stores-universal/context';
 
 
 import {
 import {
-  currentPageDataAtom, currentPageIdAtom, pageErrorAtom, pageLoadingAtom, pageNotCreatableAtom, pageNotFoundAtom,
+  currentPageDataAtom,
+  currentPageIdAtom,
+  pageErrorAtom,
+  pageLoadingAtom,
+  pageNotCreatableAtom,
+  pageNotFoundAtom,
+  shareLinkIdAtom,
 } from './internal-atoms';
 } from './internal-atoms';
 
 
 type FetchPageArgs = {
 type FetchPageArgs = {
-  path?: string,
-  pageId?: string,
-  revisionId?: string,
-}
+  path?: string;
+  pageId?: string;
+  revisionId?: string;
+};
 
 
 /**
 /**
  * Simplified page fetching hook using Jotai state management
  * Simplified page fetching hook using Jotai state management
  * All state is managed through atoms for consistent global state
  * All state is managed through atoms for consistent global state
  */
  */
 export const useFetchCurrentPage = (): {
 export const useFetchCurrentPage = (): {
-  fetchCurrentPage: (args?: FetchPageArgs) => Promise<IPagePopulatedToShowRevision | null>,
-  isLoading: boolean,
-  error: Error | null,
+  fetchCurrentPage: (
+    args?: FetchPageArgs,
+  ) => Promise<IPagePopulatedToShowRevision | null>;
+  isLoading: boolean;
+  error: Error | null;
 } => {
 } => {
-  const { data: shareLinkId } = useShareLinkId();
+  const shareLinkId = useAtomValue(shareLinkIdAtom);
 
 
-  // Use atoms for state instead of local state
   const isLoading = useAtomValue(pageLoadingAtom);
   const isLoading = useAtomValue(pageLoadingAtom);
   const error = useAtomValue(pageErrorAtom);
   const error = useAtomValue(pageErrorAtom);
 
 
   const fetchCurrentPage = useAtomCallback(
   const fetchCurrentPage = useAtomCallback(
-    useCallback(async(get, set, args?: FetchPageArgs): Promise<IPagePopulatedToShowRevision | null> => {
-      const currentPageId = get(currentPageIdAtom);
-      const currentPageData = get(currentPageDataAtom);
+    useCallback(
+      async (
+        get,
+        set,
+        args?: FetchPageArgs,
+      ): Promise<IPagePopulatedToShowRevision | null> => {
+        const currentPageId = get(currentPageIdAtom);
+        const currentPageData = get(currentPageDataAtom);
+
+        // Process path first to handle permalinks
+        let decodedPath: string | undefined;
+        if (args?.path != null) {
+          try {
+            decodedPath = decodeURIComponent(args.path);
+          } catch (e) {
+            decodedPath = args.path;
+          }
+        }
 
 
-      // Process path first to handle permalinks
-      let decodedPath: string | undefined;
-      if (args?.path != null) {
-        try {
-          decodedPath = decodeURIComponent(args.path);
+        // Guard clause to prevent unnecessary fetching
+        if (args?.pageId != null && args.pageId === currentPageId) {
+          return currentPageData ?? null;
         }
         }
-        catch (e) {
-          decodedPath = args.path;
+        if (decodedPath != null) {
+          if (
+            isPermalink(decodedPath) &&
+            removeHeadingSlash(decodedPath) === currentPageId
+          ) {
+            return currentPageData ?? null;
+          }
+          if (decodedPath === currentPageData?.path) {
+            return currentPageData ?? null;
+          }
         }
         }
-      }
-
-      // Guard clause to prevent unnecessary fetching
-      if (args?.pageId != null && args.pageId === currentPageId) {
-        return currentPageData ?? null;
-      }
-      if (decodedPath != null) {
-        if (isPermalink(decodedPath) && removeHeadingSlash(decodedPath) === currentPageId) {
-          return currentPageData ?? null;
+
+        set(pageLoadingAtom, true);
+        set(pageErrorAtom, null);
+
+        // determine parameters
+        const pageId = args?.pageId;
+        const revisionId =
+          args?.revisionId ??
+          (isClient()
+            ? new URLSearchParams(window.location.search).get('revisionId')
+            : undefined);
+
+        // params for API
+        const params: {
+          path?: string;
+          pageId?: string;
+          revisionId?: string;
+          shareLinkId?: string;
+        } = {};
+        if (shareLinkId != null) {
+          params.shareLinkId = shareLinkId;
         }
         }
-        if (decodedPath === currentPageData?.path) {
-          return currentPageData ?? null;
+        if (revisionId != null) {
+          params.revisionId = revisionId;
         }
         }
-      }
-
-      set(pageLoadingAtom, true);
-      set(pageErrorAtom, null);
-
-      // determine parameters
-      const pageId = args?.pageId;
-      const revisionId = args?.revisionId ?? (isClient() ? new URLSearchParams(window.location.search).get('revisionId') : undefined);
-
-      // params for API
-      const params: { path?: string, pageId?: string, revisionId?: string, shareLinkId?: string } = {};
-      if (shareLinkId != null) {
-        params.shareLinkId = shareLinkId;
-      }
-      if (revisionId != null) {
-        params.revisionId = revisionId;
-      }
-
-      // priority: pageId > permalink > path
-      if (pageId != null) {
-        params.pageId = pageId;
-      }
-      else if (decodedPath != null && isPermalink(decodedPath)) {
-        params.pageId = removeHeadingSlash(decodedPath);
-      }
-      else if (decodedPath != null) {
-        params.path = decodedPath;
-      }
-      // if args is empty, get from global state
-      else if (currentPageId != null) {
-        params.pageId = currentPageId;
-      }
-      else if (isClient()) {
-        try {
-          params.path = decodeURIComponent(window.location.pathname);
+
+        // priority: pageId > permalink > path
+        if (pageId != null) {
+          params.pageId = pageId;
+        } else if (decodedPath != null && isPermalink(decodedPath)) {
+          params.pageId = removeHeadingSlash(decodedPath);
+        } else if (decodedPath != null) {
+          params.path = decodedPath;
         }
         }
-        catch (e) {
-          params.path = window.location.pathname;
+        // if args is empty, get from global state
+        else if (currentPageId != null) {
+          params.pageId = currentPageId;
+        } else if (isClient()) {
+          try {
+            params.path = decodeURIComponent(window.location.pathname);
+          } catch (e) {
+            params.path = window.location.pathname;
+          }
+        } else {
+          // TODO: https://github.com/weseek/growi/pull/9118
+          // throw new Error('Either path or pageId must be provided when not in a browser environment');
+          set(pageLoadingAtom, false);
+          return null;
         }
         }
-      }
-      else {
-        // TODO: https://github.com/weseek/growi/pull/9118
-        // throw new Error('Either path or pageId must be provided when not in a browser environment');
-        set(pageLoadingAtom, false);
-        return null;
-      }
-
-      try {
-        const { data } = await apiv3Get<{ page: IPagePopulatedToShowRevision }>('/page', params);
-        const { page: newData } = data;
-
-        set(currentPageDataAtom, newData);
-        set(currentPageIdAtom, newData._id);
-        set(pageNotFoundAtom, false);
-        set(pageNotCreatableAtom, false);
-
-        return newData;
-      }
-      catch (err) {
-        set(pageErrorAtom, err as Error);
-
-        const apiError = err as any; // eslint-disable-line @typescript-eslint/no-explicit-any
-        if (apiError.response?.status === 404) {
-          set(pageNotFoundAtom, true);
-          if (params.path != null) {
-            set(pageNotCreatableAtom, !isCreatablePage(params.path));
+
+        try {
+          const { data } = await apiv3Get<{
+            page: IPagePopulatedToShowRevision;
+          }>('/page', params);
+          const { page: newData } = data;
+
+          set(currentPageDataAtom, newData);
+          set(currentPageIdAtom, newData._id);
+          set(pageNotFoundAtom, false);
+          set(pageNotCreatableAtom, false);
+
+          return newData;
+        } catch (err) {
+          set(pageErrorAtom, err as Error);
+
+          const apiError = err as any; // eslint-disable-line @typescript-eslint/no-explicit-any
+          if (apiError.response?.status === 404) {
+            set(pageNotFoundAtom, true);
+            if (params.path != null) {
+              set(pageNotCreatableAtom, !isCreatablePage(params.path));
+            }
           }
           }
+        } finally {
+          set(pageLoadingAtom, false);
         }
         }
-      }
-      finally {
-        set(pageLoadingAtom, false);
-      }
 
 
-      return null;
-    }, [shareLinkId]),
+        return null;
+      },
+      [shareLinkId],
+    ),
   );
   );
 
 
   return { fetchCurrentPage, isLoading, error };
   return { fetchCurrentPage, isLoading, error };

+ 0 - 12
apps/app/src/stores-universal/context.tsx

@@ -18,22 +18,10 @@ declare global {
 type Nullable<T> = T | null;
 type Nullable<T> = T | null;
 
 
 
 
-export const useIsSharedUser = (initialData?: boolean): SWRResponse<boolean, Error> => {
-  return useContextSWR<boolean, Error>('isSharedUser', initialData);
-};
-
-export const useShareLinkId = (initialData?: string): SWRResponse<string, Error> => {
-  return useContextSWR('shareLinkId', initialData);
-};
-
 export const useRegistrationWhitelist = (initialData?: Nullable<string[]>): SWRResponse<Nullable<string[]>, Error> => {
 export const useRegistrationWhitelist = (initialData?: Nullable<string[]>): SWRResponse<Nullable<string[]>, Error> => {
   return useContextSWR<Nullable<string[]>, Error>('registrationWhitelist', initialData);
   return useContextSWR<Nullable<string[]>, Error>('registrationWhitelist', initialData);
 };
 };
 
 
-export const useIsSearchPage = (initialData?: Nullable<boolean>) : SWRResponse<Nullable<boolean>, Error> => {
-  return useContextSWR<Nullable<boolean>, Error>('isSearchPage', initialData);
-};
-
 export const useIsMailerSetup = (initialData?: boolean): SWRResponse<boolean, Error> => {
 export const useIsMailerSetup = (initialData?: boolean): SWRResponse<boolean, Error> => {
   return useContextSWR('isMailerSetup', initialData);
   return useContextSWR('isMailerSetup', initialData);
 };
 };

+ 2 - 2
apps/app/src/stores/page.tsx

@@ -20,7 +20,7 @@ import type { IPagePathWithDescendantCount } from '~/interfaces/page';
 import type { IRecordApplicableGrant, IResCurrentGrantData } from '~/interfaces/page-grant';
 import type { IRecordApplicableGrant, IResCurrentGrantData } from '~/interfaces/page-grant';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
 import { useIsGuestUser, useIsReadOnlyUser } from '~/states/context';
 import { usePageNotFound } from '~/states/page';
 import { usePageNotFound } from '~/states/page';
-import { useShareLinkId } from '~/stores-universal/context';
+import { useShareLinkId } from '~/states/page/hooks';
 import type { AxiosResponse } from '~/utils/axios';
 import type { AxiosResponse } from '~/utils/axios';
 
 
 import type { IPageTagsInfo } from '../interfaces/tag';
 import type { IPageTagsInfo } from '../interfaces/tag';
@@ -53,7 +53,7 @@ export const useSWRxPageByPath = (path?: string, config?: SWRConfiguration): SWR
 };
 };
 
 
 export const useSWRxTagsInfo = (pageId: Nullable<string>, config?: SWRConfiguration): SWRResponse<IPageTagsInfo | null, Error> => {
 export const useSWRxTagsInfo = (pageId: Nullable<string>, config?: SWRConfiguration): SWRResponse<IPageTagsInfo | null, Error> => {
-  const { data: shareLinkId } = useShareLinkId();
+  const [shareLinkId] = useShareLinkId();
 
 
   const endpoint = `/pages.getPageTag?pageId=${pageId}`;
   const endpoint = `/pages.getPageTag?pageId=${pageId}`;
 
 

+ 8 - 7
apps/app/src/stores/ui.tsx

@@ -17,15 +17,15 @@ import useSWRImmutable from 'swr/immutable';
 
 
 import type { IPageSelectedGrant } from '~/interfaces/page';
 import type { IPageSelectedGrant } from '~/interfaces/page';
 import type { UpdateDescCountData } from '~/interfaces/websocket';
 import type { UpdateDescCountData } from '~/interfaces/websocket';
-import { useIsEditable, useIsIdenticalPath, useIsReadOnlyUser } from '~/states/context';
+import {
+  useIsEditable, useIsIdenticalPath, useIsReadOnlyUser, useIsSharedUser,
+} from '~/states/context';
 import { useCurrentUser } from '~/states/global';
 import { useCurrentUser } from '~/states/global';
 import {
 import {
   usePageNotFound, useCurrentPagePath, useIsTrashPage, useCurrentPageId,
   usePageNotFound, useCurrentPagePath, useIsTrashPage, useCurrentPageId,
 } from '~/states/page';
 } from '~/states/page';
+import { useShareLinkId } from '~/states/page/hooks';
 import { EditorMode, useEditorMode } from '~/states/ui/editor';
 import { EditorMode, useEditorMode } from '~/states/ui/editor';
-import {
-  useIsSharedUser, useShareLinkId,
-} from '~/stores-universal/context';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import { useStaticSWR } from './use-static-swr';
 import { useStaticSWR } from './use-static-swr';
@@ -257,7 +257,7 @@ export const useIsAbleToShowPageManagement = (): SWRResponse<boolean, Error> =>
   const [currentPageId] = useCurrentPageId();
   const [currentPageId] = useCurrentPageId();
   const [isNotFound] = usePageNotFound();
   const [isNotFound] = usePageNotFound();
   const [_isTrashPage] = useIsTrashPage();
   const [_isTrashPage] = useIsTrashPage();
-  const { data: _isSharedUser } = useIsSharedUser();
+  const [_isSharedUser] = useIsSharedUser();
 
 
   const pageId = currentPageId;
   const pageId = currentPageId;
   const includesUndefined = [pageId, _isTrashPage, _isSharedUser, isNotFound].some(v => v === undefined);
   const includesUndefined = [pageId, _isTrashPage, _isSharedUser, isNotFound].some(v => v === undefined);
@@ -279,7 +279,8 @@ export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
   const [currentPagePath] = useCurrentPagePath();
   const [currentPagePath] = useCurrentPagePath();
   const [isIdenticalPath] = useIsIdenticalPath();
   const [isIdenticalPath] = useIsIdenticalPath();
   const { editorMode } = useEditorMode();
   const { editorMode } = useEditorMode();
-  const { data: shareLinkId } = useShareLinkId();
+  const [shareLinkId] = useShareLinkId();
+
 
 
   const includesUndefined = [currentPagePath, isIdenticalPath, isNotFound, editorMode].some(v => v === undefined);
   const includesUndefined = [currentPagePath, isIdenticalPath, isNotFound, editorMode].some(v => v === undefined);
 
 
@@ -296,7 +297,7 @@ export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
 export const useIsAbleToChangeEditorMode = (): SWRResponse<boolean, Error> => {
 export const useIsAbleToChangeEditorMode = (): SWRResponse<boolean, Error> => {
   const key = 'isAbleToChangeEditorMode';
   const key = 'isAbleToChangeEditorMode';
   const [isEditable] = useIsEditable();
   const [isEditable] = useIsEditable();
-  const { data: isSharedUser } = useIsSharedUser();
+  const [isSharedUser] = useIsSharedUser();
 
 
   const includesUndefined = [isEditable, isSharedUser].some(v => v === undefined);
   const includesUndefined = [isEditable, isSharedUser].some(v => v === undefined);
 
 

+ 153 - 21
pnpm-lock.yaml

@@ -330,7 +330,7 @@ importers:
         version: 3.9.1
         version: 3.9.1
       babel-plugin-superjson-next:
       babel-plugin-superjson-next:
         specifier: ^0.4.2
         specifier: ^0.4.2
-        version: 0.4.5(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)
+        version: 0.4.5(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@2.2.2)
       body-parser:
       body-parser:
         specifier: ^1.20.3
         specifier: ^1.20.3
         version: 1.20.3
         version: 1.20.3
@@ -542,8 +542,8 @@ importers:
         specifier: ^15.3.1
         specifier: ^15.3.1
         version: 15.3.1(i18next@23.16.5)(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
         version: 15.3.1(i18next@23.16.5)(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
       next-superjson:
       next-superjson:
-        specifier: ^0.0.4
-        version: 0.0.4(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15)))
+        specifier: ^1.0.7
+        version: 1.0.7(@swc/helpers@0.5.15)(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@2.2.2)
       next-themes:
       next-themes:
         specifier: ^0.2.1
         specifier: ^0.2.1
         version: 0.2.1(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
         version: 0.2.1(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -707,8 +707,8 @@ importers:
         specifier: '=4.2.2'
         specifier: '=4.2.2'
         version: 4.2.2
         version: 4.2.2
       superjson:
       superjson:
-        specifier: ^1.9.1
-        version: 1.13.3
+        specifier: ^2.2.2
+        version: 2.2.2
       swagger-jsdoc:
       swagger-jsdoc:
         specifier: ^6.2.8
         specifier: ^6.2.8
         version: 6.2.8(openapi-types@12.1.3)
         version: 6.2.8(openapi-types@12.1.3)
@@ -5014,60 +5014,120 @@ packages:
     cpu: [arm64]
     cpu: [arm64]
     os: [darwin]
     os: [darwin]
 
 
+  '@swc/core-darwin-arm64@1.4.17':
+    resolution: {integrity: sha512-HVl+W4LezoqHBAYg2JCqR+s9ife9yPfgWSj37iIawLWzOmuuJ7jVdIB7Ee2B75bEisSEKyxRlTl6Y1Oq3owBgw==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [darwin]
+
   '@swc/core-darwin-x64@1.10.7':
   '@swc/core-darwin-x64@1.10.7':
     resolution: {integrity: sha512-RFIAmWVicD/l3RzxgHW0R/G1ya/6nyMspE2cAeDcTbjHi0I5qgdhBWd6ieXOaqwEwiCd0Mot1g2VZrLGoBLsjQ==}
     resolution: {integrity: sha512-RFIAmWVicD/l3RzxgHW0R/G1ya/6nyMspE2cAeDcTbjHi0I5qgdhBWd6ieXOaqwEwiCd0Mot1g2VZrLGoBLsjQ==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
     cpu: [x64]
     cpu: [x64]
     os: [darwin]
     os: [darwin]
 
 
+  '@swc/core-darwin-x64@1.4.17':
+    resolution: {integrity: sha512-WYRO9Fdzq4S/he8zjW5I95G1zcvyd9yyD3Tgi4/ic84P5XDlSMpBDpBLbr/dCPjmSg7aUXxNQqKqGkl6dQxYlA==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [darwin]
+
   '@swc/core-linux-arm-gnueabihf@1.10.7':
   '@swc/core-linux-arm-gnueabihf@1.10.7':
     resolution: {integrity: sha512-QP8vz7yELWfop5mM5foN6KkLylVO7ZUgWSF2cA0owwIaziactB2hCPZY5QU690coJouk9KmdFsPWDnaCFUP8tg==}
     resolution: {integrity: sha512-QP8vz7yELWfop5mM5foN6KkLylVO7ZUgWSF2cA0owwIaziactB2hCPZY5QU690coJouk9KmdFsPWDnaCFUP8tg==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
     cpu: [arm]
     cpu: [arm]
     os: [linux]
     os: [linux]
 
 
+  '@swc/core-linux-arm-gnueabihf@1.4.17':
+    resolution: {integrity: sha512-cgbvpWOvtMH0XFjvwppUCR+Y+nf6QPaGu6AQ5hqCP+5Lv2zO5PG0RfasC4zBIjF53xgwEaaWmGP5/361P30X8Q==}
+    engines: {node: '>=10'}
+    cpu: [arm]
+    os: [linux]
+
   '@swc/core-linux-arm64-gnu@1.10.7':
   '@swc/core-linux-arm64-gnu@1.10.7':
     resolution: {integrity: sha512-NgUDBGQcOeLNR+EOpmUvSDIP/F7i/OVOKxst4wOvT5FTxhnkWrW+StJGKj+DcUVSK5eWOYboSXr1y+Hlywwokw==}
     resolution: {integrity: sha512-NgUDBGQcOeLNR+EOpmUvSDIP/F7i/OVOKxst4wOvT5FTxhnkWrW+StJGKj+DcUVSK5eWOYboSXr1y+Hlywwokw==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
     cpu: [arm64]
     cpu: [arm64]
     os: [linux]
     os: [linux]
 
 
+  '@swc/core-linux-arm64-gnu@1.4.17':
+    resolution: {integrity: sha512-l7zHgaIY24cF9dyQ/FOWbmZDsEj2a9gRFbmgx2u19e3FzOPuOnaopFj0fRYXXKCmtdx+anD750iBIYnTR+pq/Q==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [linux]
+
   '@swc/core-linux-arm64-musl@1.10.7':
   '@swc/core-linux-arm64-musl@1.10.7':
     resolution: {integrity: sha512-gp5Un3EbeSThBIh6oac5ZArV/CsSmTKj5jNuuUAuEsML3VF9vqPO+25VuxCvsRf/z3py+xOWRaN2HY/rjMeZog==}
     resolution: {integrity: sha512-gp5Un3EbeSThBIh6oac5ZArV/CsSmTKj5jNuuUAuEsML3VF9vqPO+25VuxCvsRf/z3py+xOWRaN2HY/rjMeZog==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
     cpu: [arm64]
     cpu: [arm64]
     os: [linux]
     os: [linux]
 
 
+  '@swc/core-linux-arm64-musl@1.4.17':
+    resolution: {integrity: sha512-qhH4gr9gAlVk8MBtzXbzTP3BJyqbAfUOATGkyUtohh85fPXQYuzVlbExix3FZXTwFHNidGHY8C+ocscI7uDaYw==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [linux]
+
   '@swc/core-linux-x64-gnu@1.10.7':
   '@swc/core-linux-x64-gnu@1.10.7':
     resolution: {integrity: sha512-k/OxLLMl/edYqbZyUNg6/bqEHTXJT15l9WGqsl/2QaIGwWGvles8YjruQYQ9d4h/thSXLT9gd8bExU2D0N+bUA==}
     resolution: {integrity: sha512-k/OxLLMl/edYqbZyUNg6/bqEHTXJT15l9WGqsl/2QaIGwWGvles8YjruQYQ9d4h/thSXLT9gd8bExU2D0N+bUA==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
     cpu: [x64]
     cpu: [x64]
     os: [linux]
     os: [linux]
 
 
+  '@swc/core-linux-x64-gnu@1.4.17':
+    resolution: {integrity: sha512-vRDFATL1oN5oZMImkwbgSHEkp8xG1ofEASBypze01W1Tqto8t+yo6gsp69wzCZBlxldsvPpvFZW55Jq0Rn+UnA==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [linux]
+
   '@swc/core-linux-x64-musl@1.10.7':
   '@swc/core-linux-x64-musl@1.10.7':
     resolution: {integrity: sha512-XeDoURdWt/ybYmXLCEE8aSiTOzEn0o3Dx5l9hgt0IZEmTts7HgHHVeRgzGXbR4yDo0MfRuX5nE1dYpTmCz0uyA==}
     resolution: {integrity: sha512-XeDoURdWt/ybYmXLCEE8aSiTOzEn0o3Dx5l9hgt0IZEmTts7HgHHVeRgzGXbR4yDo0MfRuX5nE1dYpTmCz0uyA==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
     cpu: [x64]
     cpu: [x64]
     os: [linux]
     os: [linux]
 
 
+  '@swc/core-linux-x64-musl@1.4.17':
+    resolution: {integrity: sha512-zQNPXAXn3nmPqv54JVEN8k2JMEcMTQ6veVuU0p5O+A7KscJq+AGle/7ZQXzpXSfUCXlLMX4wvd+rwfGhh3J4cw==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [linux]
+
   '@swc/core-win32-arm64-msvc@1.10.7':
   '@swc/core-win32-arm64-msvc@1.10.7':
     resolution: {integrity: sha512-nYAbi/uLS+CU0wFtBx8TquJw2uIMKBnl04LBmiVoFrsIhqSl+0MklaA9FVMGA35NcxSJfcm92Prl2W2LfSnTqQ==}
     resolution: {integrity: sha512-nYAbi/uLS+CU0wFtBx8TquJw2uIMKBnl04LBmiVoFrsIhqSl+0MklaA9FVMGA35NcxSJfcm92Prl2W2LfSnTqQ==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
     cpu: [arm64]
     cpu: [arm64]
     os: [win32]
     os: [win32]
 
 
+  '@swc/core-win32-arm64-msvc@1.4.17':
+    resolution: {integrity: sha512-z86n7EhOwyzxwm+DLE5NoLkxCTme2lq7QZlDjbQyfCxOt6isWz8rkW5QowTX8w9Rdmk34ncrjSLvnHOeLY17+w==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [win32]
+
   '@swc/core-win32-ia32-msvc@1.10.7':
   '@swc/core-win32-ia32-msvc@1.10.7':
     resolution: {integrity: sha512-+aGAbsDsIxeLxw0IzyQLtvtAcI1ctlXVvVcXZMNXIXtTURM876yNrufRo4ngoXB3jnb1MLjIIjgXfFs/eZTUSw==}
     resolution: {integrity: sha512-+aGAbsDsIxeLxw0IzyQLtvtAcI1ctlXVvVcXZMNXIXtTURM876yNrufRo4ngoXB3jnb1MLjIIjgXfFs/eZTUSw==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
     cpu: [ia32]
     cpu: [ia32]
     os: [win32]
     os: [win32]
 
 
+  '@swc/core-win32-ia32-msvc@1.4.17':
+    resolution: {integrity: sha512-JBwuSTJIgiJJX6wtr4wmXbfvOswHFj223AumUrK544QV69k60FJ9q2adPW9Csk+a8wm1hLxq4HKa2K334UHJ/g==}
+    engines: {node: '>=10'}
+    cpu: [ia32]
+    os: [win32]
+
   '@swc/core-win32-x64-msvc@1.10.7':
   '@swc/core-win32-x64-msvc@1.10.7':
     resolution: {integrity: sha512-TBf4clpDBjF/UUnkKrT0/th76/zwvudk5wwobiTFqDywMApHip5O0VpBgZ+4raY2TM8k5+ujoy7bfHb22zu17Q==}
     resolution: {integrity: sha512-TBf4clpDBjF/UUnkKrT0/th76/zwvudk5wwobiTFqDywMApHip5O0VpBgZ+4raY2TM8k5+ujoy7bfHb22zu17Q==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
     cpu: [x64]
     cpu: [x64]
     os: [win32]
     os: [win32]
 
 
+  '@swc/core-win32-x64-msvc@1.4.17':
+    resolution: {integrity: sha512-jFkOnGQamtVDBm3MF5Kq1lgW8vx4Rm1UvJWRUfg+0gx7Uc3Jp3QMFeMNw/rDNQYRDYPG3yunCC+2463ycd5+dg==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [win32]
+
   '@swc/core@1.10.7':
   '@swc/core@1.10.7':
     resolution: {integrity: sha512-py91kjI1jV5D5W/Q+PurBdGsdU5TFbrzamP7zSCqLdMcHkKi3rQEM5jkQcZr0MXXSJTaayLxS3MWYTBIkzPDrg==}
     resolution: {integrity: sha512-py91kjI1jV5D5W/Q+PurBdGsdU5TFbrzamP7zSCqLdMcHkKi3rQEM5jkQcZr0MXXSJTaayLxS3MWYTBIkzPDrg==}
     engines: {node: '>=10'}
     engines: {node: '>=10'}
@@ -5077,6 +5137,15 @@ packages:
       '@swc/helpers':
       '@swc/helpers':
         optional: true
         optional: true
 
 
+  '@swc/core@1.4.17':
+    resolution: {integrity: sha512-tq+mdWvodMBNBBZbwFIMTVGYHe9N7zvEaycVVjfvAx20k1XozHbHhRv+9pEVFJjwRxLdXmtvFZd3QZHRAOpoNQ==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      '@swc/helpers': ^0.5.0
+    peerDependenciesMeta:
+      '@swc/helpers':
+        optional: true
+
   '@swc/counter@0.1.3':
   '@swc/counter@0.1.3':
     resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
     resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
 
 
@@ -5092,6 +5161,9 @@ packages:
     peerDependencies:
     peerDependencies:
       '@swc/core': '*'
       '@swc/core': '*'
 
 
+  '@swc/types@0.1.12':
+    resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==}
+
   '@swc/types@0.1.17':
   '@swc/types@0.1.17':
     resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==}
     resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==}
 
 
@@ -11521,8 +11593,14 @@ packages:
       react: '>= 17.0.2'
       react: '>= 17.0.2'
       react-i18next: '>= 13.5.0'
       react-i18next: '>= 13.5.0'
 
 
-  next-superjson@0.0.4:
-    resolution: {integrity: sha512-PYtoHbPcZYED8Vm9YCIQIZi/arANNnf6grwjkPuJXzWdY1TxJxrn9dCPmVj6ALvPn9YcDThwEA9WvHq/NyzMvw==}
+  next-superjson-plugin@0.6.3:
+    resolution: {integrity: sha512-gipGROzbbn1Koq84AZQodIvBdORp9dytIDv07SguwXdxnJb6v05KCmHVNU9L6AWqxjP14qNIWCNdKRDhnGRZrg==}
+    peerDependencies:
+      next: ^13.0 || ^14.0
+      superjson: ^1 || ^2
+
+  next-superjson@1.0.7:
+    resolution: {integrity: sha512-07zs+A+oyCmpJm4qwo5M8pjnBIrYgnd2eox77wafB1shdCSxbaHNwejCKMK2e2sPtJ1u+iNPG1bG4mO6xwOz6g==}
     peerDependencies:
     peerDependencies:
       next: '>=10'
       next: '>=10'
 
 
@@ -13804,9 +13882,9 @@ packages:
     resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==}
     resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==}
     engines: {node: '>=14.18.0'}
     engines: {node: '>=14.18.0'}
 
 
-  superjson@1.13.3:
-    resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==}
-    engines: {node: '>=10'}
+  superjson@2.2.2:
+    resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
+    engines: {node: '>=16'}
 
 
   supertest@7.1.4:
   supertest@7.1.4:
     resolution: {integrity: sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==}
     resolution: {integrity: sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==}
@@ -20204,33 +20282,63 @@ snapshots:
   '@swc/core-darwin-arm64@1.10.7':
   '@swc/core-darwin-arm64@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-darwin-arm64@1.4.17':
+    optional: true
+
   '@swc/core-darwin-x64@1.10.7':
   '@swc/core-darwin-x64@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-darwin-x64@1.4.17':
+    optional: true
+
   '@swc/core-linux-arm-gnueabihf@1.10.7':
   '@swc/core-linux-arm-gnueabihf@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-linux-arm-gnueabihf@1.4.17':
+    optional: true
+
   '@swc/core-linux-arm64-gnu@1.10.7':
   '@swc/core-linux-arm64-gnu@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-linux-arm64-gnu@1.4.17':
+    optional: true
+
   '@swc/core-linux-arm64-musl@1.10.7':
   '@swc/core-linux-arm64-musl@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-linux-arm64-musl@1.4.17':
+    optional: true
+
   '@swc/core-linux-x64-gnu@1.10.7':
   '@swc/core-linux-x64-gnu@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-linux-x64-gnu@1.4.17':
+    optional: true
+
   '@swc/core-linux-x64-musl@1.10.7':
   '@swc/core-linux-x64-musl@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-linux-x64-musl@1.4.17':
+    optional: true
+
   '@swc/core-win32-arm64-msvc@1.10.7':
   '@swc/core-win32-arm64-msvc@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-win32-arm64-msvc@1.4.17':
+    optional: true
+
   '@swc/core-win32-ia32-msvc@1.10.7':
   '@swc/core-win32-ia32-msvc@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-win32-ia32-msvc@1.4.17':
+    optional: true
+
   '@swc/core-win32-x64-msvc@1.10.7':
   '@swc/core-win32-x64-msvc@1.10.7':
     optional: true
     optional: true
 
 
+  '@swc/core-win32-x64-msvc@1.4.17':
+    optional: true
+
   '@swc/core@1.10.7(@swc/helpers@0.5.15)':
   '@swc/core@1.10.7(@swc/helpers@0.5.15)':
     dependencies:
     dependencies:
       '@swc/counter': 0.1.3
       '@swc/counter': 0.1.3
@@ -20248,6 +20356,23 @@ snapshots:
       '@swc/core-win32-x64-msvc': 1.10.7
       '@swc/core-win32-x64-msvc': 1.10.7
       '@swc/helpers': 0.5.15
       '@swc/helpers': 0.5.15
 
 
+  '@swc/core@1.4.17(@swc/helpers@0.5.15)':
+    dependencies:
+      '@swc/counter': 0.1.3
+      '@swc/types': 0.1.17
+    optionalDependencies:
+      '@swc/core-darwin-arm64': 1.4.17
+      '@swc/core-darwin-x64': 1.4.17
+      '@swc/core-linux-arm-gnueabihf': 1.4.17
+      '@swc/core-linux-arm64-gnu': 1.4.17
+      '@swc/core-linux-arm64-musl': 1.4.17
+      '@swc/core-linux-x64-gnu': 1.4.17
+      '@swc/core-linux-x64-musl': 1.4.17
+      '@swc/core-win32-arm64-msvc': 1.4.17
+      '@swc/core-win32-ia32-msvc': 1.4.17
+      '@swc/core-win32-x64-msvc': 1.4.17
+      '@swc/helpers': 0.5.15
+
   '@swc/counter@0.1.3': {}
   '@swc/counter@0.1.3': {}
 
 
   '@swc/helpers@0.5.15':
   '@swc/helpers@0.5.15':
@@ -20266,6 +20391,10 @@ snapshots:
       '@swc/counter': 0.1.3
       '@swc/counter': 0.1.3
       jsonc-parser: 3.2.0
       jsonc-parser: 3.2.0
 
 
+  '@swc/types@0.1.12':
+    dependencies:
+      '@swc/counter': 0.1.3
+
   '@swc/types@0.1.17':
   '@swc/types@0.1.17':
     dependencies:
     dependencies:
       '@swc/counter': 0.1.3
       '@swc/counter': 0.1.3
@@ -22222,13 +22351,13 @@ snapshots:
       '@types/babel__core': 7.20.5
       '@types/babel__core': 7.20.5
       '@types/babel__traverse': 7.0.7
       '@types/babel__traverse': 7.0.7
 
 
-  babel-plugin-superjson-next@0.4.5(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3):
+  babel-plugin-superjson-next@0.4.5(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@2.2.2):
     dependencies:
     dependencies:
       '@babel/helper-module-imports': 7.24.6
       '@babel/helper-module-imports': 7.24.6
       '@babel/types': 7.25.6
       '@babel/types': 7.25.6
       hoist-non-react-statics: 3.3.2
       hoist-non-react-statics: 3.3.2
       next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
       next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
-      superjson: 1.13.3
+      superjson: 2.2.2
 
 
   babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.6):
   babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.6):
     dependencies:
     dependencies:
@@ -28047,18 +28176,21 @@ snapshots:
       react: 18.2.0
       react: 18.2.0
       react-i18next: 15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
       react-i18next: 15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
 
 
-  next-superjson@0.0.4(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)(webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15))):
+  next-superjson-plugin@0.6.3(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@2.2.2):
     dependencies:
     dependencies:
-      '@babel/core': 7.24.6
-      '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.6)
-      '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.6)
-      babel-loader: 8.3.0(@babel/core@7.24.6)(webpack@5.92.1(@swc/core@1.10.7(@swc/helpers@0.5.15)))
-      babel-plugin-superjson-next: 0.4.5(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@1.13.3)
+      hoist-non-react-statics: 3.3.2
       next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
       next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
+      superjson: 2.2.2
+
+  next-superjson@1.0.7(@swc/helpers@0.5.15)(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@2.2.2):
+    dependencies:
+      '@swc/core': 1.4.17(@swc/helpers@0.5.15)
+      '@swc/types': 0.1.12
+      next: 14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6)
+      next-superjson-plugin: 0.6.3(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(superjson@2.2.2)
     transitivePeerDependencies:
     transitivePeerDependencies:
+      - '@swc/helpers'
       - superjson
       - superjson
-      - supports-color
-      - webpack
 
 
   next-themes@0.2.1(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
   next-themes@0.2.1(next@14.2.30(@babel/core@7.24.6)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.77.6))(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
     dependencies:
     dependencies:
@@ -30858,7 +30990,7 @@ snapshots:
     transitivePeerDependencies:
     transitivePeerDependencies:
       - supports-color
       - supports-color
 
 
-  superjson@1.13.3:
+  superjson@2.2.2:
     dependencies:
     dependencies:
       copy-anything: 3.0.5
       copy-anything: 3.0.5