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:
- Button (
packages/ui/src/button/button.tsx): Typed, accessible button variants - Spinners (
packages/ui/src/spinners/circular-spinner.tsx): Loading state indicators - Modals and Tables: Complex layout primitives
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) |
Navigation and Progress Indicators
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.tswithssr: false - State: MobX stores exposed through
@plane/shared-statefor predictable global state management - Styling: Tailwind CSS with custom configuration in
@plane/tailwind-configand components in@plane/ui - Data: SWR for caching and revalidation, backed by
@plane/servicesfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →