Optimize web app performance and memory usage with various improvements

Co-authored-by: natemkelley <natemkelley@gmail.com>
This commit is contained in:
Cursor Agent 2025-08-07 23:45:35 +00:00
parent 8987ca17a1
commit 567e1ae48c
9 changed files with 63 additions and 34 deletions

View File

@ -34,3 +34,25 @@ You can check out [the Next.js GitHub repository](https://github.com/vercel/next
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
## Profiling memory and bundles
- Development without Turbopack (can reduce baseline RAM):
```bash
pnpm --filter @buster-app/web dev:no-turbo
```
- Start with Node inspector and higher old space to capture heap snapshots:
```bash
pnpm --filter @buster-app/web build && pnpm --filter @buster-app/web start:inspect
# open chrome://inspect and take heap snapshots at startup and after actions
```
- Analyze client/server bundles:
```bash
pnpm --filter @buster-app/web build:analyze
# then open .next/analyze/client.html and server.html
```

View File

@ -5,8 +5,11 @@
"type": "module",
"scripts": {
"dev": "next dev --turbo",
"dev:no-turbo": "NEXT_TELEMETRY_DISABLED=1 next dev",
"build": "next build",
"start": "next start",
"start:inspect": "NODE_OPTIONS='--inspect --max-old-space-size=4096' next start",
"build:analyze": "ANALYZE=true next build",
"lint": "next lint && npx prettier --write . '!src/components/ui/icons/**' --log-level error",
"lint:eslint": "eslint .",
"lint:ci": "npm run lint && npm run typecheck",

View File

@ -16,7 +16,11 @@ import { configureMonacoToUseYaml } from './yamlHelper';
//import NightOwnTheme from 'monaco-themes/themes/Night Owl.json';
//https://github.com/brijeshb42/monaco-ace-tokenizer
import { Editor } from '@monaco-editor/react';
import dynamic from 'next/dynamic';
const Editor = dynamic(() => import('@monaco-editor/react').then((m) => m.Editor), {
ssr: false,
loading: () => null
});
import { useTheme } from 'next-themes';
interface AppCodeEditorProps {

View File

@ -72,12 +72,8 @@ export const getCodeTokens = async (
});
};
// Pre-initialize highlighter on module load for better performance
if (typeof window !== 'undefined') {
initializeHighlighter().catch((error) => {
console.warn('Failed to pre-initialize syntax highlighter:', error);
});
}
// Note: Do not pre-initialize on module load to keep initial memory low. Highlighter will
// initialize on first use via getHighlightedCode/getCodeTokens.
export const getFallbackStyle = (isDarkMode: boolean) => {
return {

View File

@ -1,7 +1,7 @@
import { z } from 'zod';
import { isServer } from '@tanstack/react-query';
if (!isServer) {
// Ensure this file is only evaluated on the server
if (typeof window !== 'undefined') {
throw new Error('env.mjs is only meant to be used on the server');
}

View File

@ -5,7 +5,6 @@ import type { UseSupabaseUserContextType } from '@/lib/supabase';
import { BusterAssetsProvider } from './Assets/BusterAssetsProvider';
import { AppLayoutProvider } from './BusterAppLayout';
import { BusterReactQueryProvider } from './BusterReactQuery/BusterReactQueryAndApi';
import { BusterWebSocketProvider } from './BusterWebSocket';
import { BusterNewChatProvider } from './Chats';
import { BusterPosthogProvider } from './Posthog';
import { RoutePrefetcher } from './RoutePrefetcher';
@ -24,6 +23,7 @@ export const AppProviders: React.FC<
queryClient: QueryClient;
}>
> = ({ children, queryClient, supabaseContext }) => {
const enableRoutePrefetch = process.env.NEXT_PUBLIC_ENABLE_ROUTE_PREFETCH !== 'false';
return (
<SupabaseContextProvider supabaseContext={supabaseContext}>
<BusterReactQueryProvider>
@ -33,7 +33,7 @@ export const AppProviders: React.FC<
<BusterAssetsProvider>
<BusterNewChatProvider>
<BusterPosthogProvider>{children}</BusterPosthogProvider>
<RoutePrefetcher />
{enableRoutePrefetch && <RoutePrefetcher />}
</BusterNewChatProvider>
</BusterAssetsProvider>
</BusterUserConfigProvider>

View File

@ -1,4 +1,5 @@
export const PREFETCH_STALE_TIME = 1000 * 10; // 10 seconds
export const ERROR_RETRY_DELAY = 1 * 1000; // 1 second delay after error
export const GC_TIME = 1000 * 60 * 60 * 24 * 3; // 24 hours - matches persistence duration
// Reduce browser GC time to 30 minutes to lower memory residency
export const GC_TIME = typeof window === 'undefined' ? 1000 * 60 * 60 * 24 * 3 : 1000 * 60 * 30;
export const USER_CANCELLED_ERROR = new Error('User cancelled');

View File

@ -1,10 +1,6 @@
'use client';
import { isServer } from '@tanstack/react-query';
import type { PostHogConfig } from 'posthog-js';
import posthog from 'posthog-js';
import { PostHogProvider } from 'posthog-js/react';
import React, { type PropsWithChildren, useEffect } from 'react';
import React, { type PropsWithChildren, useEffect, useMemo, useState } from 'react';
import { isDev } from '@/config';
import { useUserConfigContextSelector } from '../Users';
import type { Team } from '@buster/server-shared/teams';
@ -21,13 +17,10 @@ export const BusterPosthogProvider: React.FC<PropsWithChildren> = React.memo(({
});
BusterPosthogProvider.displayName = 'BusterPosthogProvider';
const options: Partial<PostHogConfig> = {
const makeOptions = () => ({
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
person_profiles: 'always',
session_recording: {
recordBody: true
},
session_recording: { recordBody: true },
loaded: () => {
console.log(
'%c🚀 Welcome to Buster',
@ -38,27 +31,37 @@ const options: Partial<PostHogConfig> = {
'background: #6b21a8; color: white; font-size: 10px; font-weight: normal; padding: 8px; border-radius: 4px;'
);
}
};
});
const PosthogWrapper: React.FC<PropsWithChildren> = ({ children }) => {
const user = useUserConfigContextSelector((state) => state.user);
const userTeams = useUserConfigContextSelector((state) => state.userTeams);
const userOrganizations = useUserConfigContextSelector((state) => state.userOrganizations);
const team: Team | undefined = userTeams?.[0];
const [posthogApi, setPosthogApi] = useState<any | null>(null);
const [Provider, setProvider] = useState<React.ComponentType<{ client: any }> | null>(null);
useEffect(() => {
if (POSTHOG_KEY && !isServer && user && posthog && team) {
posthog.init(POSTHOG_KEY, options);
let mounted = true;
(async () => {
if (!POSTHOG_KEY || !user || !team) return;
const [{ default: posthog }, { PostHogProvider }] = await Promise.all([
import('posthog-js'),
import('posthog-js/react')
]);
if (!mounted) return;
posthog.init(POSTHOG_KEY, makeOptions());
setPosthogApi(posthog);
setProvider(() => PostHogProvider);
const email = user.email;
posthog.identify(email, {
user,
organization: userOrganizations,
team
});
posthog.identify(email, { user, organization: userOrganizations, team });
posthog.group(team?.id, team?.name);
}
})();
return () => {
mounted = false;
};
}, [user?.id, team?.id]);
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
if (!Provider || !posthogApi) return <>{children}</>;
return <Provider client={posthogApi}>{children}</Provider>;
};

File diff suppressed because one or more lines are too long