️ perf: make app page as static route to improve performance (#5478)

* move files

* refactor to static mode

* fix tests
This commit is contained in:
Arvin Xu
2025-02-06 18:32:10 +08:00
committed by GitHub
parent 1953220ac6
commit 159f25526d
586 changed files with 564 additions and 315 deletions
+5
View File
@@ -176,6 +176,11 @@ const nextConfig: NextConfig = {
permanent: false,
source: '/repos',
},
{
destination: '/files',
permanent: false,
source: '/repos',
},
],
// when external packages in dev mode with turbopack, this config will lead to bundle error
serverExternalPackages: isProd ? ['@electric-sql/pglite'] : undefined,
+1 -1
View File
@@ -317,7 +317,7 @@
"vitest": "~1.2.2",
"vitest-canvas-mock": "^0.3.3"
},
"packageManager": "pnpm@9.15.4",
"packageManager": "pnpm@10.2.0",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
@@ -4,7 +4,8 @@ import { Center } from 'react-layout-kit';
import BrandWatermark from '@/components/BrandWatermark';
import { metadataModule } from '@/server/metadata';
import { translation } from '@/server/translation';
import { isMobileDevice } from '@/utils/server/responsive';
import { DynamicLayoutProps } from '@/types/next';
import { RouteVariants } from '@/utils/server/routeVariants';
import Category from './features/Category';
import UserBanner from './features/UserBanner';
@@ -17,10 +18,10 @@ export const generateMetadata = async () => {
});
};
const Page = async () => {
const mobile = await isMobileDevice();
const Page = async (props: DynamicLayoutProps) => {
const isMobile = await RouteVariants.getIsMobile(props);
if (!mobile) return redirect('/chat');
if (!isMobile) return redirect('/chat');
return (
<>
@@ -36,3 +37,5 @@ const Page = async () => {
Page.displayName = 'Me';
export default Page;
export const dynamic = 'force-static';
@@ -9,7 +9,7 @@ import Cell, { CellProps } from '@/components/Cell';
import { isDeprecatedEdition } from '@/const/version';
import { ProfileTabs } from '@/store/global/initialState';
import { useUserStore } from '@/store/user';
import { authSelectors } from '@/store/user/slices/auth/selectors';
import { authSelectors } from '@/store/user/selectors';
const Category = memo(() => {
const [isLogin, enableAuth, isLoginWithClerk, signOut] = useUserStore((s) => [
@@ -2,7 +2,8 @@ import { redirect } from 'next/navigation';
import { metadataModule } from '@/server/metadata';
import { translation } from '@/server/translation';
import { isMobileDevice } from '@/utils/server/responsive';
import { DynamicLayoutProps } from '@/types/next';
import { RouteVariants } from '@/utils/server/routeVariants';
import Category from './features/Category';
@@ -15,10 +16,10 @@ export const generateMetadata = async () => {
});
};
const Page = async () => {
const mobile = await isMobileDevice();
const Page = async (props: DynamicLayoutProps) => {
const isMobile = await RouteVariants.getIsMobile(props);
if (!mobile) return redirect('/profile');
if (!isMobile) return redirect('/profile');
return <Category />;
};
@@ -26,3 +27,5 @@ const Page = async () => {
Page.displayName = 'MeProfile';
export default Page;
export const dynamic = 'force-static';
@@ -2,7 +2,8 @@ import { redirect } from 'next/navigation';
import { metadataModule } from '@/server/metadata';
import { translation } from '@/server/translation';
import { isMobileDevice } from '@/utils/server/responsive';
import { DynamicLayoutProps } from '@/types/next';
import { RouteVariants } from '@/utils/server/routeVariants';
import Category from './features/Category';
@@ -15,10 +16,10 @@ export const generateMetadata = async () => {
});
};
const Page = async () => {
const mobile = await isMobileDevice();
const Page = async (props: DynamicLayoutProps) => {
const isMobile = await RouteVariants.getIsMobile(props);
if (!mobile) return redirect('/settings/common');
if (!isMobile) return redirect('/settings/common');
return <Category />;
};
@@ -26,3 +27,5 @@ const Page = async () => {
Page.displayName = 'MeSettings';
export default Page;
export const dynamic = 'force-static';
@@ -2,7 +2,7 @@
import { SideNav } from '@lobehub/ui';
import { parseAsBoolean, useQueryState } from 'nuqs';
import { memo } from 'react';
import { Suspense, memo } from 'react';
import { useActiveTabKey } from '@/hooks/useActiveTabKey';
import { useGlobalStore } from '@/store/global';
@@ -14,11 +14,16 @@ import BottomActions from './BottomActions';
import PinList from './PinList';
import TopActions from './TopActions';
const Nav = memo(() => {
const Top = () => {
const [isPinned] = useQueryState('pinned', parseAsBoolean);
const sidebarKey = useActiveTabKey();
return <TopActions isPinned={isPinned} tab={sidebarKey} />;
};
const Nav = memo(() => {
const inZenMode = useGlobalStore(systemStatusSelectors.inZenMode);
const { showPinList } = useServerConfigStore(featureFlagsSelectors);
const [isPinned] = useQueryState('pinned', parseAsBoolean);
return (
!inZenMode && (
@@ -27,10 +32,10 @@ const Nav = memo(() => {
bottomActions={<BottomActions />}
style={{ height: '100%', zIndex: 100 }}
topActions={
<>
<TopActions isPinned={isPinned} tab={sidebarKey} />
<Suspense>
<Top />
{showPinList && <PinList />}
</>
</Suspense>
}
/>
)
@@ -2,9 +2,9 @@
import dynamic from 'next/dynamic';
import { usePathname } from 'next/navigation';
import qs from 'query-string';
import { PropsWithChildren, memo } from 'react';
import { withSuspense } from '@/components/withSuspense';
import { useShowMobileWorkspace } from '@/hooks/useShowMobileWorkspace';
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
@@ -24,8 +24,7 @@ const MOBILE_NAV_ROUTES = new Set([
const Layout = memo(({ children }: PropsWithChildren) => {
const showMobileWorkspace = useShowMobileWorkspace();
const pathname = usePathname();
const { url } = qs.parseUrl(pathname);
const showNav = !showMobileWorkspace && MOBILE_NAV_ROUTES.has(url);
const showNav = !showMobileWorkspace && MOBILE_NAV_ROUTES.has(pathname);
const { showCloudPromotion } = useServerConfigStore(featureFlagsSelectors);
@@ -40,4 +39,4 @@ const Layout = memo(({ children }: PropsWithChildren) => {
Layout.displayName = 'MobileMainLayout';
export default Layout;
export default withSuspense(Layout);
@@ -2,6 +2,7 @@
import { useLayoutEffect } from 'react';
import { withSuspense } from '@/components/withSuspense';
import { useQueryRoute } from '@/hooks/useQueryRoute';
/**
@@ -10,7 +11,7 @@ import { useQueryRoute } from '@/hooks/useQueryRoute';
* @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
*/
const ChangelogModalFallback = () => {
const ChangelogModal = () => {
const router = useQueryRoute();
useLayoutEffect(() => {
@@ -20,4 +21,4 @@ const ChangelogModalFallback = () => {
return null;
};
export default ChangelogModalFallback;
export default withSuspense(ChangelogModal);
@@ -4,7 +4,7 @@ import { Fragment, Suspense } from 'react';
import { Flexbox } from 'react-layout-kit';
import urlJoin from 'url-join';
import Pagination from '@/app/@modal/(.)changelog/modal/features/Pagination';
import Pagination from '@/app/[variants]/@modal/(.)changelog/modal/features/Pagination';
import StructuredData from '@/components/StructuredData';
import { serverFeatureFlags } from '@/config/featureFlags';
import { BRANDING_NAME } from '@/const/branding';
@@ -13,7 +13,8 @@ import { ldModule } from '@/server/ld';
import { metadataModule } from '@/server/metadata';
import { ChangelogService } from '@/server/services/changelog';
import { translation } from '@/server/translation';
import { isMobileDevice } from '@/utils/server/responsive';
import { DynamicLayoutProps } from '@/types/next';
import { RouteVariants } from '@/utils/server/routeVariants';
import GridLayout from './features/GridLayout';
import Post from './features/Post';
@@ -28,12 +29,12 @@ export const generateMetadata = async () => {
});
};
const Page = async () => {
const Page = async (props: DynamicLayoutProps) => {
const hideDocs = serverFeatureFlags().hideDocs;
if (hideDocs) return notFound();
const mobile = await isMobileDevice();
const isMobile = await RouteVariants.getIsMobile(props);
const { t, locale } = await translation('metadata');
const changelogService = new ChangelogService();
const data = await changelogService.getChangelogIndex();
@@ -49,7 +50,7 @@ const Page = async () => {
return (
<>
<StructuredData ld={ld} />
<Flexbox gap={mobile ? 16 : 48}>
<Flexbox gap={isMobile ? 16 : 48}>
{data?.map((item) => (
<Fragment key={item.id}>
<Suspense
@@ -60,7 +61,7 @@ const Page = async () => {
</GridLayout>
}
>
<Post locale={locale} mobile={mobile} {...item} />
<Post locale={locale} mobile={isMobile} {...item} />
</Suspense>
</Fragment>
))}
@@ -1,4 +1,5 @@
import { isMobileDevice } from '@/utils/server/responsive';
import { DynamicLayoutProps } from '@/types/next';
import { RouteVariants } from '@/utils/server/routeVariants';
import ChatHydration from './features/ChatHydration';
import ChatInput from './features/ChatInput';
@@ -6,14 +7,14 @@ import ChatList from './features/ChatList';
import ThreadHydration from './features/ThreadHydration';
import ZenModeToast from './features/ZenModeToast';
const ChatConversation = async () => {
const mobile = await isMobileDevice();
const ChatConversation = async (props: DynamicLayoutProps) => {
const isMobile = await RouteVariants.getIsMobile(props);
return (
<>
<ZenModeToast />
<ChatList mobile={mobile} />
<ChatInput mobile={mobile} />
<ChatList mobile={isMobile} />
<ChatInput mobile={isMobile} />
<ChatHydration />
<ThreadHydration />
</>
@@ -1,17 +1,18 @@
import React, { Suspense, lazy } from 'react';
import Loading from '@/components/Loading/BrandTextLoading';
import { isMobileDevice } from '@/utils/server/responsive';
import { DynamicLayoutProps } from '@/types/next';
import { RouteVariants } from '@/utils/server/routeVariants';
import Desktop from './_layout/Desktop';
import Mobile from './_layout/Mobile';
const PortalBody = lazy(() => import('@/features/Portal/router'));
const Inspector = async () => {
const mobile = await isMobileDevice();
const Inspector = async (props: DynamicLayoutProps) => {
const isMobile = await RouteVariants.getIsMobile(props);
const Layout = mobile ? Mobile : Desktop;
const Layout = isMobile ? Mobile : Desktop;
return (
<Suspense fallback={<Loading />}>
@@ -1,7 +1,8 @@
// import TopicListContent from './features/TopicListContent';
import React, { Suspense, lazy } from 'react';
import { isMobileDevice } from '@/utils/server/responsive';
import { DynamicLayoutProps } from '@/types/next';
import { RouteVariants } from '@/utils/server/routeVariants';
import Desktop from './_layout/Desktop';
import Mobile from './_layout/Mobile';
@@ -10,14 +11,14 @@ import SystemRole from './features/SystemRole';
const TopicContent = lazy(() => import('./features/TopicListContent'));
const Topic = async () => {
const mobile = await isMobileDevice();
const Topic = async (props: DynamicLayoutProps) => {
const isMobile = await RouteVariants.getIsMobile(props);
const Layout = mobile ? Mobile : Desktop;
const Layout = isMobile ? Mobile : Desktop;
return (
<>
{!mobile && <SystemRole />}
{!isMobile && <SystemRole />}
<Layout>
<Suspense fallback={<SkeletonList />}>
<TopicContent />

Some files were not shown because too many files have changed in this diff Show More