# React Performance Optimizations in Plane's Frontend: 8 Techniques from the Open Source Codebase

> Discover 8 React performance optimizations in Plane's open source frontend including memoization code splitting MobX and virtualized lists to boost UI thread speed.

- Repository: [Plane/plane](https://github.com/makeplane/plane)
- Tags: performance
- Published: 2026-06-18

---

**Plane's React frontend combines component memoization, route-level code splitting, MobX state management, virtualized lists, and build-time optimizations via Vite to minimize UI thread work and maintain snappy performance across large workspaces.**

Plane is an open-source project management platform built to handle large teams and extensive issue lists without slowing down. The frontend architecture relies on a deliberate combination of React patterns and build optimizations to keep the interface responsive even under heavy data loads. This article examines the specific **performance optimizations** implemented in the `makeplane/plane` repository, referencing actual source files and implementation details.

## Component Memoization with React.memo

Plane wraps leaf UI components in `React.memo` to prevent unnecessary virtual-DOM diffing when props remain unchanged. This technique is applied consistently across the codebase to isolate re-renders.

In [`apps/web/core/components/pages/editor/toolbar/toolbar.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/components/pages/editor/toolbar/toolbar.tsx), the toolbar button component is memoized to avoid re-rendering during parent updates:

```typescript
// apps/web/core/components/pages/editor/toolbar/toolbar.tsx
import React from 'react';

const ToolbarButton = React.memo(function ToolbarButton(props: ToolbarButtonProps) {
  // …render logic…
});

```

Similarly, breadcrumb fragments in [`apps/web/core/components/common/breadcrumb-link.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/components/common/breadcrumb-link.tsx) use memoization to maintain stable rendering performance within navigation hierarchies.

## Stabilizing References with useCallback and useMemo

To guarantee stable references for props passed to memoized children, Plane uses `useCallback` for event handlers and `useMemo` for derived data. This pattern prevents memoized components from re-rendering due to new function references.

For example, event handlers in editor toolbars stabilize their references like this:

```typescript
const handleClick = React.useCallback(() => {
  store.toggleSidebar();
}, [store]);   // store is a MobX observable; the callback only changes if the store ref changes

```

Complex list filters and calculations are cached with `useMemo`, ensuring expensive operations run only when dependencies change rather than on every paint.

## Route-Level Code Splitting

Plane reduces initial JavaScript payload by implementing route-level code splitting with `React.lazy` and `Suspense`. Heavy pages such as the analytics dashboard are fetched on demand.

The routing configuration defines lazy-loaded components as seen in [`apps/web/react-router.config.ts`](https://github.com/makeplane/plane/blob/main/apps/web/react-router.config.ts):

```typescript
// apps/web/react-router.config.ts
const AnalyticsPage = React.lazy(() => import('./pages/analytics'));
...
<Route path="/analytics" element={<Suspense fallback={<Spinner/>}><AnalyticsPage/></Suspense>} />

```

This approach defers loading heavy editor and analytics bundles until the user navigates to those routes, improving first-paint metrics.

## Build-Time Optimization with Vite

The Vite configuration in [`apps/web/vite.config.ts`](https://github.com/makeplane/plane/blob/main/apps/web/vite.config.ts) implements manual chunking to separate vendor libraries from application code. This setup leverages ES-module pre-bundling and tree-shaking to eliminate dead code.

Key optimizations include:
- **Manual chunks** that isolate libraries like `react` and `mobx` into separate cacheable bundles
- **Dynamic imports** that split code at logical boundaries
- **ESM pre-bundling** for faster module resolution in the browser

These build-time **performance optimizations** result in smaller runtime bundles and better caching across deployments.

## Fine-Grained State Management with MobX

Plane uses MobX observables via the `packages/shared-state` package to achieve fine-grained reactivity. Unlike centralized stores that broadcast updates to all subscribers, MobX causes only components reading specific state slices to re-render.

The store implementation in [`packages/shared-state/src/store.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/store.ts) defines observable state that components subscribe to precisely:

```typescript
// packages/shared-state/src/store.ts
// MobX observables trigger updates only in dependent components
class Store {
  @observable issues: Issue[] = [];
  
  @action updateIssue(id: string, data: Partial<Issue>) {
    // Only components displaying this specific issue re-render
  }
}

```

This pattern dramatically lowers the number of components updating after state changes compared to traditional Redux architectures.

## Virtualized Lists for Large Datasets

To handle thousands of issues without DOM bloat, Plane implements virtualized lists using `react-window`. The component in [`apps/web/core/components/common/virtual-list.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/components/common/virtual-list.tsx) renders only visible viewport rows.

The implementation follows this pattern:

```typescript
import { FixedSizeList as List } from 'react-window';

<List
  height={400}
  itemCount={issues.length}
  itemSize={48}
  width="100%"
>
  {({ index, style }) => <IssueRow style={style} issue={issues[index]} />}
</List>

```

This changes rendering complexity from O(totalRows) to O(visibleRows), maintaining smooth scroll performance regardless of total dataset size.

## Deferred Loading and Input Optimization

Plane defers non-critical work using intersection observers and throttling techniques.

**Lazy asset loading** via `IntersectionObserver` (implemented in [`apps/web/hooks/use-lazy-image.ts`](https://github.com/makeplane/plane/blob/main/apps/web/hooks/use-lazy-image.ts)) loads images and large SVG icons only when they enter the viewport, deferring network traffic and decoding work.

**Debounced inputs** in search fields and filter pickers use `lodash.debounce` via [`apps/web/hooks/useDebouncedSearch.ts`](https://github.com/makeplane/plane/blob/main/apps/web/hooks/useDebouncedSearch.ts) to prevent flooding the UI thread with rapid successive updates while users type or drag elements.

## Summary

- **Component memoization** with `React.memo` in [`toolbar.tsx`](https://github.com/makeplane/plane/blob/main/toolbar.tsx) and [`breadcrumb-link.tsx`](https://github.com/makeplane/plane/blob/main/breadcrumb-link.tsx) prevents unnecessary re-renders of leaf components.
- **Stable callbacks** via `useCallback` ensure memoized children only update when data actually changes.
- **Route-level code splitting** with `React.lazy` reduces initial bundle size for heavy pages like analytics.
- **Vite manual chunks** and tree-shaking in [`vite.config.ts`](https://github.com/makeplane/plane/blob/main/vite.config.ts) optimize delivery and caching of vendor libraries.
- **MobX observables** in `packages/shared-state` enable fine-grained reactivity that limits render scope to only affected components.
- **Virtualized lists** using `react-window` in [`virtual-list.tsx`](https://github.com/makeplane/plane/blob/main/virtual-list.tsx) scale to thousands of rows without DOM overhead.
- **Lazy image loading** and **debounced inputs** defer work until necessary, keeping the main thread responsive.

## Frequently Asked Questions

### How does Plane prevent unnecessary re-renders in React components?

Plane combines `React.memo` for leaf components with `useCallback` for event handlers to ensure components only re-render when their props actually change. This is implemented in files like [`apps/web/core/components/pages/editor/toolbar/toolbar.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/components/pages/editor/toolbar/toolbar.tsx) where toolbar buttons are wrapped in `React.memo`, preventing virtual-DOM diff work during parent updates.

### What technique does Plane use to handle large issue lists efficiently?

The frontend uses `react-window` virtualization as seen in [`apps/web/core/components/common/virtual-list.tsx`](https://github.com/makeplane/plane/blob/main/apps/web/core/components/common/virtual-list.tsx) to render only visible viewport rows. This approach changes rendering complexity from O(totalRows) to O(visibleRows), allowing smooth scrolling through thousands of issues without DOM bloat.

### How does Plane reduce the initial JavaScript bundle size?

Plane implements route-level code splitting using `React.lazy` and `Suspense` for heavy pages like the analytics dashboard, loaded on demand from [`apps/web/react-router.config.ts`](https://github.com/makeplane/plane/blob/main/apps/web/react-router.config.ts). Additionally, the Vite configuration in [`apps/web/vite.config.ts`](https://github.com/makeplane/plane/blob/main/apps/web/vite.config.ts) uses manual chunks to separate vendor libraries (React, MobX) from application code, enabling better caching and smaller initial payloads.

### Why does Plane use MobX instead of Redux for state management?

Plane uses MobX observables in [`packages/shared-state/src/store.ts`](https://github.com/makeplane/plane/blob/main/packages/shared-state/src/store.ts) to achieve fine-grained reactivity where only components reading specific state slices re-render. Compared to Redux, which typically broadcasts updates to all connected components, MobX dramatically reduces the number of components that need to update after state changes, improving UI responsiveness in large workspaces.