Yuki Takei 7 месяцев назад
Родитель
Сommit
8791b373c0

+ 50 - 74
apps/app/src/pages/[[...path]]/common-helpers.ts

@@ -41,62 +41,16 @@ export async function createNextI18NextConfig(
   );
   );
 }
 }
 
 
-// Common user and redirect handling
-type RedirectResult = { redirect: { permanent: boolean; destination: string } };
-export function handleUserAndRedirects(context: GetServerSidePropsContext, props: Record<string, unknown>): RedirectResult | null {
-  const req = context.req as CrowiRequest;
-  const { user } = req;
-
-  // Add current user if exists
-  if (user != null) {
-    props.currentUser = user.toObject();
-  }
-
-  // Check for redirect destination
-  const redirectDestination = props.redirectDestination;
-  if (typeof redirectDestination === 'string') {
-    return {
-      redirect: {
-        permanent: false,
-        destination: redirectDestination,
-      },
-    };
-  }
-
-  return null;
-}
-
-// Helper function to handle page data result with improved type safety
-export function handlePageDataResult<T, U>(
-    result: GetServerSidePropsResult<T>,
-    currentProps: U,
-): GetServerSidePropsResult<U & T> {
-  if ('redirect' in result) {
-    return result as GetServerSidePropsResult<U & T>;
-  }
-  if ('notFound' in result) {
-    return result as GetServerSidePropsResult<U & T>;
-  }
-
-  // Ensure result.props exists and is not a Promise
-  if (!('props' in result) || !result.props) {
-    throw new Error('Invalid page data result - missing props');
-  }
-
-  // Type-safe props merging
-  return {
-    props: {
-      ...currentProps,
-      ...result.props,
-    } as U & T,
-  };
-}
-
 // Type-safe GetServerSidePropsResult merger with overloads
 // Type-safe GetServerSidePropsResult merger with overloads
 export function mergeGetServerSidePropsResults<T1, T2>(
 export function mergeGetServerSidePropsResults<T1, T2>(
     result1: GetServerSidePropsResult<T1>,
     result1: GetServerSidePropsResult<T1>,
     result2: GetServerSidePropsResult<T2>,
     result2: GetServerSidePropsResult<T2>,
 ): GetServerSidePropsResult<T1 & T2>;
 ): GetServerSidePropsResult<T1 & T2>;
+export function mergeGetServerSidePropsResults<T1, T2, T3>(
+    result1: GetServerSidePropsResult<T1>,
+    result2: GetServerSidePropsResult<T2>,
+    result3: GetServerSidePropsResult<T3>,
+): GetServerSidePropsResult<T1 & T2 & T3>;
 export function mergeGetServerSidePropsResults<T1, T2, T3, T4>(
 export function mergeGetServerSidePropsResults<T1, T2, T3, T4>(
     result1: GetServerSidePropsResult<T1>,
     result1: GetServerSidePropsResult<T1>,
     result2: GetServerSidePropsResult<T2>,
     result2: GetServerSidePropsResult<T2>,
@@ -108,7 +62,7 @@ export function mergeGetServerSidePropsResults<T1, T2, T3, T4>(
     result2: GetServerSidePropsResult<T2>,
     result2: GetServerSidePropsResult<T2>,
     result3?: GetServerSidePropsResult<T3>,
     result3?: GetServerSidePropsResult<T3>,
     result4?: GetServerSidePropsResult<T4>,
     result4?: GetServerSidePropsResult<T4>,
-): GetServerSidePropsResult<T1 & T2> | GetServerSidePropsResult<T1 & T2 & T3 & T4> {
+): GetServerSidePropsResult<T1 & T2> | GetServerSidePropsResult<T1 & T2 & T3> | GetServerSidePropsResult<T1 & T2 & T3 & T4> {
   // Handle 2-argument case
   // Handle 2-argument case
   if (result3 === undefined && result4 === undefined) {
   if (result3 === undefined && result4 === undefined) {
     if ('redirect' in result1) return result1;
     if ('redirect' in result1) return result1;
@@ -128,34 +82,56 @@ export function mergeGetServerSidePropsResults<T1, T2, T3, T4>(
     };
     };
   }
   }
 
 
-  // Handle 4-argument case
-  if (result3 === undefined || result4 === undefined) {
-    throw new Error('All 4 arguments required for 4-way merge');
+  // Handle 3-argument case
+  if (result4 === undefined && result3 !== undefined) {
+    if ('redirect' in result1) return result1;
+    if ('redirect' in result2) return result2;
+    if ('redirect' in result3) return result3;
+    if ('notFound' in result1) return result1;
+    if ('notFound' in result2) return result2;
+    if ('notFound' in result3) return result3;
+
+    if (!('props' in result1) || !('props' in result2) || !('props' in result3)) {
+      throw new Error('Invalid GetServerSidePropsResult - missing props');
+    }
+
+    return {
+      props: {
+        ...result1.props,
+        ...result2.props,
+        ...result3.props,
+      } as T1 & T2 & T3,
+    };
   }
   }
 
 
-  if ('redirect' in result1) return result1;
-  if ('redirect' in result2) return result2;
-  if ('redirect' in result3) return result3;
-  if ('redirect' in result4) return result4;
+  // Handle 4-argument case
+  if (result3 !== undefined && result4 !== undefined) {
+    if ('redirect' in result1) return result1;
+    if ('redirect' in result2) return result2;
+    if ('redirect' in result3) return result3;
+    if ('redirect' in result4) return result4;
+
+    if ('notFound' in result1) return result1;
+    if ('notFound' in result2) return result2;
+    if ('notFound' in result3) return result3;
+    if ('notFound' in result4) return result4;
 
 
-  if ('notFound' in result1) return result1;
-  if ('notFound' in result2) return result2;
-  if ('notFound' in result3) return result3;
-  if ('notFound' in result4) return result4;
+    if (!('props' in result1) || !('props' in result2)
+        || !('props' in result3) || !('props' in result4)) {
+      throw new Error('Invalid GetServerSidePropsResult - missing props');
+    }
 
 
-  if (!('props' in result1) || !('props' in result2)
-      || !('props' in result3) || !('props' in result4)) {
-    throw new Error('Invalid GetServerSidePropsResult - missing props');
+    return {
+      props: {
+        ...result1.props,
+        ...result2.props,
+        ...result3.props,
+        ...result4.props,
+      } as T1 & T2 & T3 & T4,
+    };
   }
   }
 
 
-  return {
-    props: {
-      ...result1.props,
-      ...result2.props,
-      ...result3.props,
-      ...result4.props,
-    } as T1 & T2 & T3 & T4,
-  };
+  throw new Error('Invalid arguments for mergeGetServerSidePropsResults');
 }
 }
 
 
 // Type-safe property extraction helper
 // Type-safe property extraction helper

+ 46 - 36
apps/app/src/pages/[[...path]]/server-side-props.ts

@@ -1,9 +1,9 @@
 import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
 import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
 
 
 import { addActivity } from '~/pages/utils/activity';
 import { addActivity } from '~/pages/utils/activity';
+import { getServerSideI18nProps } from '~/pages/utils/i18n';
 import type { PageTitleCustomizationProps } from '~/pages/utils/page-title-customization';
 import type { PageTitleCustomizationProps } from '~/pages/utils/page-title-customization';
 import { getServerSidePageTitleCustomizationProps } from '~/pages/utils/page-title-customization';
 import { getServerSidePageTitleCustomizationProps } from '~/pages/utils/page-title-customization';
-import { getServerSideSSRProps } from '~/pages/utils/ssr';
 import type { ServerConfigurationInitialProps } from '~/states/server-configurations/hydrate';
 import type { ServerConfigurationInitialProps } from '~/states/server-configurations/hydrate';
 
 
 import type { CommonInitialProps, CommonEachProps } from '../utils/commons';
 import type { CommonInitialProps, CommonEachProps } from '../utils/commons';
@@ -13,8 +13,6 @@ import { getServerSideUserUISettingsProps } from '../utils/user-ui-settings';
 
 
 import {
 import {
   NEXT_JS_ROUTING_PAGE,
   NEXT_JS_ROUTING_PAGE,
-  createNextI18NextConfig,
-  handlePageDataResult,
   mergeGetServerSidePropsResults,
   mergeGetServerSidePropsResults,
 } from './common-helpers';
 } from './common-helpers';
 import { getServerSideConfigurationProps } from './configuration-props';
 import { getServerSideConfigurationProps } from './configuration-props';
@@ -63,6 +61,10 @@ type BaseInitialProps = {
 };
 };
 
 
 export async function getServerSidePropsForInitial(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<InitialProps & SameRouteEachProps>> {
 export async function getServerSidePropsForInitial(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<InitialProps & SameRouteEachProps>> {
+  //
+  // STAGE 1
+  //
+
   // Collect all required props with type safety (includes CommonEachProps now)
   // Collect all required props with type safety (includes CommonEachProps now)
   const collectedProps = await collectCombinedProps(context);
   const collectedProps = await collectCombinedProps(context);
 
 
@@ -76,6 +78,10 @@ export async function getServerSidePropsForInitial(context: GetServerSidePropsCo
     };
     };
   }
   }
 
 
+  //
+  // STAGE 2
+  //
+
   const baseProps: BaseInitialProps = {
   const baseProps: BaseInitialProps = {
     nextjsRoutingPage: NEXT_JS_ROUTING_PAGE,
     nextjsRoutingPage: NEXT_JS_ROUTING_PAGE,
     isNotFound: false,
     isNotFound: false,
@@ -90,43 +96,40 @@ export async function getServerSidePropsForInitial(context: GetServerSidePropsCo
     ...baseProps,
     ...baseProps,
   };
   };
 
 
-  // Get page data with proper error handling
-  const pageDataResult = await getPageDataForInitial(context);
-  const handleResult = handlePageDataResult(pageDataResult, initialProps);
+  //
+  // STAGE 3
+  //
 
 
-  // Check for early return (redirect/notFound)
-  if ('redirect' in handleResult || 'notFound' in handleResult) {
-    return handleResult as GetServerSidePropsResult<InitialProps & SameRouteEachProps>;
-  }
+  // Create a result with initial props
+  const initialPropsResult: GetServerSidePropsResult<typeof initialProps> = {
+    props: initialProps,
+  };
 
 
-  // Type assertion to access properties before full validation
-  const mergedProps = handleResult.props as InitialProps & SameRouteEachProps;
+  // Get page data and i18n props concurrently
+  const [pageDataResult, i18nPropsResult] = await Promise.all([
+    getPageDataForInitial(context),
+    getServerSideI18nProps(context, ['translation']),
+  ]);
 
 
-  // Handle SSR configuration with type safety BEFORE validation
-  if (mergedProps.pageWithMeta?.data != null) {
-    const ssrPropsResult = await getServerSideSSRProps(context, mergedProps.pageWithMeta.data, ['translation']);
-    if ('props' in ssrPropsResult) {
-      Object.assign(mergedProps, ssrPropsResult.props);
-    }
-  }
-  else {
-    mergedProps.skipSSR = true;
-    const nextI18NextConfig = await createNextI18NextConfig(context, ['translation']);
-    mergedProps._nextI18Next = nextI18NextConfig._nextI18Next;
-  }
+  // Merge all results in a type-safe manner
+  const mergedResult = mergeGetServerSidePropsResults(
+    initialPropsResult,
+    pageDataResult,
+    i18nPropsResult,
+  );
 
 
-  // Ensure skipSSR is always defined
-  if (mergedProps.skipSSR === undefined) {
-    mergedProps.skipSSR = false;
+  // Check for early return (redirect/notFound)
+  if ('redirect' in mergedResult || 'notFound' in mergedResult) {
+    return mergedResult;
   }
   }
 
 
   // Type-safe props validation AFTER skipSSR is properly set
   // Type-safe props validation AFTER skipSSR is properly set
-  if (!isValidInitialAndSameRouteProps(mergedProps)) {
+  if (!isValidInitialAndSameRouteProps(mergedResult.props)) {
     throw new Error('Invalid merged props structure');
     throw new Error('Invalid merged props structure');
   }
   }
 
 
-  await addActivity(context, getAction(mergedProps));
-  return { props: mergedProps };
+  await addActivity(context, getAction(mergedResult.props));
+  return { props: mergedResult.props };
 }
 }
 
 
 export async function getServerSidePropsForSameRoute(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<SameRouteEachProps>> {
 export async function getServerSidePropsForSameRoute(context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<SameRouteEachProps>> {
@@ -156,20 +159,27 @@ export async function getServerSidePropsForSameRoute(context: GetServerSideProps
     isIdenticalPathPage: false,
     isIdenticalPathPage: false,
   };
   };
 
 
-  // Page data retrieval with improved error handling
+  // Create a result with base props
+  const basePropsResult: GetServerSidePropsResult<SameRouteEachProps> = {
+    props: baseProps,
+  };
+
+  // Get page data
   const sameRouteDataResult = await getPageDataForSameRoute(context);
   const sameRouteDataResult = await getPageDataForSameRoute(context);
-  const handleResult = handlePageDataResult(sameRouteDataResult, baseProps);
+
+  // Merge results in a type-safe manner
+  const mergedResult = mergeGetServerSidePropsResults(basePropsResult, sameRouteDataResult);
 
 
   // Check for early return (redirect/notFound)
   // Check for early return (redirect/notFound)
-  if ('redirect' in handleResult || 'notFound' in handleResult) {
-    return handleResult as GetServerSidePropsResult<SameRouteEachProps>;
+  if ('redirect' in mergedResult || 'notFound' in mergedResult) {
+    return mergedResult as GetServerSidePropsResult<SameRouteEachProps>;
   }
   }
 
 
   // Validate the merged props have all required properties
   // Validate the merged props have all required properties
-  if (!isValidSameRouteProps(handleResult.props)) {
+  if (!isValidSameRouteProps(mergedResult.props)) {
     throw new Error('Invalid same route props structure');
     throw new Error('Invalid same route props structure');
   }
   }
-  const mergedProps = handleResult.props;
+  const mergedProps = mergedResult.props;
 
 
   return { props: mergedProps };
   return { props: mergedProps };
 }
 }

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

@@ -6,7 +6,6 @@ import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { CommonEachProps, CommonInitialProps } from '~/pages/utils/commons';
 import type { CommonEachProps, CommonInitialProps } from '~/pages/utils/commons';
 import type { PageTitleCustomizationProps } from '~/pages/utils/page-title-customization';
 import type { PageTitleCustomizationProps } from '~/pages/utils/page-title-customization';
-import type { SSRProps } from '~/pages/utils/ssr';
 import type { UserUISettingsProps } from '~/pages/utils/user-ui-settings';
 import type { UserUISettingsProps } from '~/pages/utils/user-ui-settings';
 import type { PageDocument } from '~/server/models/page';
 import type { PageDocument } from '~/server/models/page';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -16,8 +15,9 @@ const logger = loggerFactory('growi:pages:[[...path]]:types');
 
 
 export type IPageToShowRevisionWithMeta = IDataWithMeta<IPagePopulatedToShowRevision & PageDocument, IPageInfo>;
 export type IPageToShowRevisionWithMeta = IDataWithMeta<IPagePopulatedToShowRevision & PageDocument, IPageInfo>;
 
 
-export type InitialProps = CommonInitialProps & SSRProps & UserUISettingsProps & {
+export type InitialProps = CommonInitialProps & UserUISettingsProps & {
   pageWithMeta: IPageToShowRevisionWithMeta | null,
   pageWithMeta: IPageToShowRevisionWithMeta | null,
+  skipSSR?: boolean,
 
 
   sidebarConfig: ISidebarConfig,
   sidebarConfig: ISidebarConfig,
   rendererConfig: RendererConfig,
   rendererConfig: RendererConfig,

+ 18 - 0
apps/app/src/pages/utils/i18n.ts

@@ -0,0 +1,18 @@
+import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
+import type { SSRConfig } from 'next-i18next';
+
+import { createNextI18NextConfig } from '../[[...path]]/common-helpers';
+
+export const getServerSideI18nProps = async(
+    context: GetServerSidePropsContext,
+    namespacesRequired?: string[] | undefined,
+): Promise<GetServerSidePropsResult<SSRConfig>> => {
+  // Use the shared helper function instead of the local one
+  const nextI18NextConfig = await createNextI18NextConfig(context, namespacesRequired);
+
+  return {
+    props: {
+      _nextI18Next: nextI18NextConfig._nextI18Next,
+    },
+  };
+};

+ 0 - 49
apps/app/src/pages/utils/ssr.ts

@@ -1,49 +0,0 @@
-import { isServer } from '@growi/core/dist/utils';
-import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next';
-import type { SSRConfig } from 'next-i18next';
-
-import type { CrowiRequest } from '~/interfaces/crowi-request';
-import type { PageDocument } from '~/server/models/page';
-
-import { createNextI18NextConfig } from '../[[...path]]/common-helpers';
-
-
-const skipSSR = async(page: PageDocument, ssrMaxRevisionBodyLength: number): Promise<boolean> => {
-  if (!isServer()) {
-    throw new Error('This method is not available on the client-side');
-  }
-
-  const latestRevisionBodyLength = await page.getLatestRevisionBodyLength();
-
-  if (latestRevisionBodyLength == null) {
-    return true;
-  }
-
-  return ssrMaxRevisionBodyLength < latestRevisionBodyLength;
-};
-
-export type SSRProps = SSRConfig & {
-  skipSSR: boolean;
-}
-
-export const getServerSideSSRProps = async(
-    context: GetServerSidePropsContext,
-    page: PageDocument,
-    namespacesRequired?: string[] | undefined,
-): Promise<GetServerSidePropsResult<SSRProps>> => {
-  const req: CrowiRequest = context.req as CrowiRequest;
-  const { crowi } = req;
-  const { configManager } = crowi;
-
-  // Use the shared helper function instead of the local one
-  const nextI18NextConfig = await createNextI18NextConfig(context, namespacesRequired);
-
-  const ssrMaxRevisionBodyLength = configManager.getConfig('app:ssrMaxRevisionBodyLength');
-
-  return {
-    props: {
-      _nextI18Next: nextI18NextConfig._nextI18Next,
-      skipSSR: await skipSSR(page, ssrMaxRevisionBodyLength),
-    },
-  };
-};