mirror of https://github.com/buster-so/buster.git
Optimize web app performance and memory usage with various improvements
Co-authored-by: natemkelley <natemkelley@gmail.com>
This commit is contained in:
parent
8987ca17a1
commit
567e1ae48c
|
@ -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.
|
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.
|
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
|
||||||
|
```
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbo",
|
"dev": "next dev --turbo",
|
||||||
|
"dev:no-turbo": "NEXT_TELEMETRY_DISABLED=1 next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"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": "next lint && npx prettier --write . '!src/components/ui/icons/**' --log-level error",
|
||||||
"lint:eslint": "eslint .",
|
"lint:eslint": "eslint .",
|
||||||
"lint:ci": "npm run lint && npm run typecheck",
|
"lint:ci": "npm run lint && npm run typecheck",
|
||||||
|
|
|
@ -16,7 +16,11 @@ import { configureMonacoToUseYaml } from './yamlHelper';
|
||||||
//import NightOwnTheme from 'monaco-themes/themes/Night Owl.json';
|
//import NightOwnTheme from 'monaco-themes/themes/Night Owl.json';
|
||||||
//https://github.com/brijeshb42/monaco-ace-tokenizer
|
//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';
|
import { useTheme } from 'next-themes';
|
||||||
|
|
||||||
interface AppCodeEditorProps {
|
interface AppCodeEditorProps {
|
||||||
|
|
|
@ -72,12 +72,8 @@ export const getCodeTokens = async (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pre-initialize highlighter on module load for better performance
|
// Note: Do not pre-initialize on module load to keep initial memory low. Highlighter will
|
||||||
if (typeof window !== 'undefined') {
|
// initialize on first use via getHighlightedCode/getCodeTokens.
|
||||||
initializeHighlighter().catch((error) => {
|
|
||||||
console.warn('Failed to pre-initialize syntax highlighter:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getFallbackStyle = (isDarkMode: boolean) => {
|
export const getFallbackStyle = (isDarkMode: boolean) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { z } from 'zod';
|
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');
|
throw new Error('env.mjs is only meant to be used on the server');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import type { UseSupabaseUserContextType } from '@/lib/supabase';
|
||||||
import { BusterAssetsProvider } from './Assets/BusterAssetsProvider';
|
import { BusterAssetsProvider } from './Assets/BusterAssetsProvider';
|
||||||
import { AppLayoutProvider } from './BusterAppLayout';
|
import { AppLayoutProvider } from './BusterAppLayout';
|
||||||
import { BusterReactQueryProvider } from './BusterReactQuery/BusterReactQueryAndApi';
|
import { BusterReactQueryProvider } from './BusterReactQuery/BusterReactQueryAndApi';
|
||||||
import { BusterWebSocketProvider } from './BusterWebSocket';
|
|
||||||
import { BusterNewChatProvider } from './Chats';
|
import { BusterNewChatProvider } from './Chats';
|
||||||
import { BusterPosthogProvider } from './Posthog';
|
import { BusterPosthogProvider } from './Posthog';
|
||||||
import { RoutePrefetcher } from './RoutePrefetcher';
|
import { RoutePrefetcher } from './RoutePrefetcher';
|
||||||
|
@ -24,6 +23,7 @@ export const AppProviders: React.FC<
|
||||||
queryClient: QueryClient;
|
queryClient: QueryClient;
|
||||||
}>
|
}>
|
||||||
> = ({ children, queryClient, supabaseContext }) => {
|
> = ({ children, queryClient, supabaseContext }) => {
|
||||||
|
const enableRoutePrefetch = process.env.NEXT_PUBLIC_ENABLE_ROUTE_PREFETCH !== 'false';
|
||||||
return (
|
return (
|
||||||
<SupabaseContextProvider supabaseContext={supabaseContext}>
|
<SupabaseContextProvider supabaseContext={supabaseContext}>
|
||||||
<BusterReactQueryProvider>
|
<BusterReactQueryProvider>
|
||||||
|
@ -33,7 +33,7 @@ export const AppProviders: React.FC<
|
||||||
<BusterAssetsProvider>
|
<BusterAssetsProvider>
|
||||||
<BusterNewChatProvider>
|
<BusterNewChatProvider>
|
||||||
<BusterPosthogProvider>{children}</BusterPosthogProvider>
|
<BusterPosthogProvider>{children}</BusterPosthogProvider>
|
||||||
<RoutePrefetcher />
|
{enableRoutePrefetch && <RoutePrefetcher />}
|
||||||
</BusterNewChatProvider>
|
</BusterNewChatProvider>
|
||||||
</BusterAssetsProvider>
|
</BusterAssetsProvider>
|
||||||
</BusterUserConfigProvider>
|
</BusterUserConfigProvider>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export const PREFETCH_STALE_TIME = 1000 * 10; // 10 seconds
|
export const PREFETCH_STALE_TIME = 1000 * 10; // 10 seconds
|
||||||
export const ERROR_RETRY_DELAY = 1 * 1000; // 1 second delay after error
|
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');
|
export const USER_CANCELLED_ERROR = new Error('User cancelled');
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { isServer } from '@tanstack/react-query';
|
import React, { type PropsWithChildren, useEffect, useMemo, useState } from 'react';
|
||||||
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 { isDev } from '@/config';
|
import { isDev } from '@/config';
|
||||||
import { useUserConfigContextSelector } from '../Users';
|
import { useUserConfigContextSelector } from '../Users';
|
||||||
import type { Team } from '@buster/server-shared/teams';
|
import type { Team } from '@buster/server-shared/teams';
|
||||||
|
@ -21,13 +17,10 @@ export const BusterPosthogProvider: React.FC<PropsWithChildren> = React.memo(({
|
||||||
});
|
});
|
||||||
BusterPosthogProvider.displayName = 'BusterPosthogProvider';
|
BusterPosthogProvider.displayName = 'BusterPosthogProvider';
|
||||||
|
|
||||||
const options: Partial<PostHogConfig> = {
|
const makeOptions = () => ({
|
||||||
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||||
person_profiles: 'always',
|
person_profiles: 'always',
|
||||||
session_recording: {
|
session_recording: { recordBody: true },
|
||||||
recordBody: true
|
|
||||||
},
|
|
||||||
|
|
||||||
loaded: () => {
|
loaded: () => {
|
||||||
console.log(
|
console.log(
|
||||||
'%c🚀 Welcome to Buster',
|
'%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;'
|
'background: #6b21a8; color: white; font-size: 10px; font-weight: normal; padding: 8px; border-radius: 4px;'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
const PosthogWrapper: React.FC<PropsWithChildren> = ({ children }) => {
|
const PosthogWrapper: React.FC<PropsWithChildren> = ({ children }) => {
|
||||||
const user = useUserConfigContextSelector((state) => state.user);
|
const user = useUserConfigContextSelector((state) => state.user);
|
||||||
const userTeams = useUserConfigContextSelector((state) => state.userTeams);
|
const userTeams = useUserConfigContextSelector((state) => state.userTeams);
|
||||||
const userOrganizations = useUserConfigContextSelector((state) => state.userOrganizations);
|
const userOrganizations = useUserConfigContextSelector((state) => state.userOrganizations);
|
||||||
const team: Team | undefined = userTeams?.[0];
|
const team: Team | undefined = userTeams?.[0];
|
||||||
|
const [posthogApi, setPosthogApi] = useState<any | null>(null);
|
||||||
|
const [Provider, setProvider] = useState<React.ComponentType<{ client: any }> | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (POSTHOG_KEY && !isServer && user && posthog && team) {
|
let mounted = true;
|
||||||
posthog.init(POSTHOG_KEY, options);
|
(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;
|
const email = user.email;
|
||||||
posthog.identify(email, {
|
posthog.identify(email, { user, organization: userOrganizations, team });
|
||||||
user,
|
|
||||||
organization: userOrganizations,
|
|
||||||
team
|
|
||||||
});
|
|
||||||
posthog.group(team?.id, team?.name);
|
posthog.group(team?.id, team?.name);
|
||||||
}
|
})();
|
||||||
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
};
|
||||||
}, [user?.id, team?.id]);
|
}, [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
Loading…
Reference in New Issue