How the Frontend is Developed in the Plane Project: React, MobX, and Monorepo Architecture

The Plane frontend is a client-side React 18 application built within a Turborepo monorepo, utilizing React Router 6 for file-based routing, MobX for state management, and a custom UI component library styled with Tailwind CSS.

The Plane project is an open-source project management platform that organizes its frontend as a modular, type-safe React application residing in the apps/web workspace. According to the Plane source code, the architecture emphasizes developer experience through automated route generation, centralized state management, and a comprehensive design system housed in internal packages.

Architecture and Technology Stack

The frontend architecture follows a modern React pattern with clear separation of concerns across multiple internal packages.

Framework and Routing

The application runs as a client-side single-page application (SPA) using React 18 with TypeScript. In apps/web/react-router.config.ts, the configuration explicitly disables server-side rendering (ssr: false), directing React Router to generate a static client bundle.

Routing follows a file-based convention where the folder structure automatically maps to URLs. Route files live under apps/web/app/(…)/ directories, with page.tsx files defining route components. For example, app/(all)/workspace-views/page.tsx automatically registers as the /workspace-views route.

State Management

Global application state is handled by MobX (mobx, mobx-react), exposed through the @plane/shared-state package. This centralized store pattern manages workspace data, user preferences, and real-time synchronization across components.

UI Library and Design System

Reusable UI components reside in the packages/ui package (published as @plane/ui). Key components include:

The design system uses Tailwind CSS with custom utility classes defined in @plane/tailwind-config, ensuring consistent theming across the monorepo.

Data Fetching and API Integration

Data synchronization uses swr for caching and revalidation, paired with @plane/services—a thin abstraction layer over the backend REST and GraphQL endpoints. This combination provides automatic caching, optimistic updates, and error retry logic.

Internationalization is handled by @plane/i18n, which loads locale JSON files at runtime for multi-language support.

Project Structure and Key Files

The Plane frontend operates within apnpm workspace monorepo structure, using workspace:* syntax for internal dependencies and catalog: aliases for external packages.

Core Configuration Files

File Purpose
apps/web/package.json Declares dependencies, scripts, and build configuration
apps/web/react-router.config.ts Configures React Router for client-side SPA mode
apps/web/app/root.tsx Root component mounting the app rail, progress bar, and outlet
apps/web/app/provider.tsx Provides MobX stores, theme context, and i18n to the application tree
apps/web/app/(all)/layout.tsx Layout wrapper applied to all routes (header, sidebar, navigation)

The AppProgressBar component (apps/web/core/lib/b-progress/AppProgressBar.tsx) integrates with React Router's useNavigation hook to display a thin top-loading bar during route transitions, providing visual feedback for asynchronous navigation states.

Development Patterns and Code Examples

Creating Routes with React Router

New pages are created by adding files to the app directory following the convention (route-group)/route-name/page.tsx:

// apps/web/app/(all)/example/page.tsx
import { useEffect } from "react";
import { useParams } from "react-router";

export default function ExamplePage() {
  const { exampleId } = useParams();

  useEffect(() => {
    console.log("Loaded example:", exampleId);
  }, [exampleId]);

  return (
    <section className="p-4">
      <h1 className="text-2xl font-bold">Example #{exampleId}</h1>
      <p className="mt-2">This page automatically maps to /example/:exampleId</p>
    </section>
  );
}

When placed at app/(all)/example/page.tsx, React Router automatically registers this as /example/:exampleId without manual route configuration.

Consuming UI Components

Components from the internal design system are imported from @plane/ui:

import { Button } from "@plane/ui";

export function SaveButton({ onSave }: { onSave: () => void }) {
  return (
    <Button
      variant="primary"
      className="rounded-md px-4 py-2"
      onClick={onSave}
    >
      Save Changes
    </Button>
  );
}

The Button component in packages/ui/src/button/button.tsx provides full TypeScript support and Tailwind styling out of the box.

Data Fetching with SWR

The recommended pattern for API interaction combines SWR with the internal services layer:

import useSWR from "swr";
import { getWorkspace } from "@plane/services";

export function WorkspaceName({ workspaceId }: { workspaceId: string }) {
  const { data, error } = useSWR(
    ["workspace", workspaceId],
    () => getWorkspace(workspaceId)
  );

  if (error) return <span className="text-red-500">Failed to load</span>;
  if (!data) return <span className="opacity-50">Loading workspace…</span>;

  return <span>{data.name}</span>;
}

The @plane/services package encapsulates endpoint logic, while SWR handles caching, deduplication, and background revalidation.

Build and Development Workflow

Development runs via pnpm dev (configured as react-router dev --port 3000), with Vite handling hot module replacement and bundling. Production builds generate static assets via react-router build, producing deployable bundles in the plane-frontend Docker image.

Testing coverage includes Storybook for UI component visualization (pnpm --filter=@plane/ui storybook) and Vitest for unit testing across packages. Continuous integration pipelines in .github/workflows/pull-request-build-lint-web-apps.yml enforce build validation and linting standards.

Summary

  • Architecture: Client-side React 18 SPA with React Router 6 file-based routing, configured in apps/web/react-router.config.ts with ssr: false
  • State: MobX stores exposed through @plane/shared-state for predictable global state management
  • Styling: Tailwind CSS with custom configuration in @plane/tailwind-config and components in @plane/ui
  • Data: SWR for caching and revalidation, backed by @plane/services for API abstraction
  • Structure: Monorepo workspace with internal packages using workspace:* references
  • Tooling: Vite bundler, TypeScript throughout, Storybook for UI development, Docker for deployment

Frequently Asked Questions

What routing system does Plane use for its frontend?

Plane uses React Router 6 with a file-based routing convention. Routes are defined by creating page.tsx files within the apps/web/app/ directory structure, where folder names map directly to URL paths. The configuration in react-router.config.ts explicitly sets ssr: false, ensuring a static client-side bundle optimized for SPA behavior.

How does Plane handle global state management?

The frontend uses MobX (mobx and mobx-react) for global state management, wrapped in the @plane/shared-state internal package. State is provided to the component tree through app/provider.tsx, which initializes stores for workspace data, user sessions, and real-time synchronization, making them accessible throughout the application without prop drilling.

Where are the reusable UI components located in the Plane codebase?

Reusable UI components reside in packages/ui (imported as @plane/ui). This package contains typed React components such as buttons (packages/ui/src/button/button.tsx), spinners (packages/ui/src/spinners/circular-spinner.tsx), modals, and tables. All components use Tailwind CSS for styling and are documented and tested via Storybook.

How does the frontend communicate with the Plane backend?

Data fetching uses swr combined with the internal @plane/services package. SWR provides caching, revalidation, and optimistic update patterns, while @plane/services abstracts the underlying REST and GraphQL endpoints. This pattern is implemented in packages/hooks with custom React hooks for specific data domains, ensuring consistent error handling and loading states across the application.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →