What Is the Web Directory in makeplane/plane? A Complete Guide to the Plane Frontend

The apps/web directory contains the entire client-side React application for Plane, serving as the browser-based interface that handles routing, state management, API communication, and UI rendering.

The Plane repository is structured as a monorepo where the web directory lives under apps/web. This folder encapsulates the single-page application (SPA) that users interact with in their browsers, built with modern React tooling and optimized for performance as a static client bundle.

Architecture and Entry Points

The Plane web application follows a convention-based routing architecture centered around React Router v7, configured explicitly for client-side rendering only.

Root Component and Routing

The entry point for the application is defined in app/root.tsx. This file establishes the HTML skeleton, injects global meta tags, preloads custom fonts via <link rel="preload">, and mounts the theme provider from next-themes. It serves as the mounting point for the React Router tree.

Routing configuration is split between react-router.config.ts and the app/routes/ directory. Critically, the configuration explicitly disables server-side rendering:

// apps/web/react-router.config.ts
export default {
  // Server-side rendering is disabled - this is a client-only SPA
  ssr: false,
  // Routes are defined in the app/routes directory
} satisfies Config;

The route tree in app/routes/** defines all navigable pages, layouts, and error boundaries. During development, the application runs on http://localhost:3000, serving the fully client-rendered interface.

Build System Configuration

The build pipeline is managed by Vite, configured in vite.config.ts. This setup processes the React Router dev plugin and handles environment variable filtering. Only client assets are emitted, reinforcing the SPA architecture:

// apps/web/vite.config.ts
export default defineConfig({
  plugins: [
    reactRouterDevPlugin(),
    // ... other plugins
  ],
  // SSR explicitly disabled in build output
  build: {
    outDir: 'build/client',
  },
});

State Management and Data Flow

State within the Plane web directory follows a structured pattern separating UI state from API communication.

MobX Stores

Domain state is maintained in MobX observable stores located in core/store/**. These stores expose observable values, computed selectors, and async actions. For example, the project store manages workspace projects:

// apps/web/core/store/project/project.store.ts
public async fetchProjects(workspaceSlug: string) {
  this.loader = "init-loader";
  const projects = await this.projectService.getProjects(workspaceSlug);
  runInAction(() => {
    projects.forEach(p => this.projectMap[p.id] = { ...this.projectMap[p.id], ...p });
    this.loader = "loaded";
    this.fetchStatus = "complete";
  });
  return projects;
}

Stores are organized by domain (projects, workspaces, webhooks) and provide the single source of truth for UI components.

API Services

The service layer in core/services/** provides thin wrappers around the Django backend API. These services handle HTTP requests using axios and return typed DTOs defined in @plane/types:

// apps/web/core/services/project.service.ts
export class ProjectService extends APIService {
  async getProjects(workspaceSlug: string): Promise<IProject[]> {
    const { data } = await this.get(`/api/workspaces/${workspaceSlug}/projects/`);
    return data;
  }
}

This separation ensures components interact with stores, while stores delegate persistence to services.

UI Components and Styling

Component Organization

While shared UI widgets reside in the separate @plane/ui workspace package, the web directory contains page-level components in app/pages/** and application-specific components in app/components/**. Components consume stores using the observer pattern from mobx-react:

// apps/web/app/(all)/[workspaceSlug]/settings/projects/page.tsx
import { observer } from "mobx-react";
import { useRootStore } from "@/hooks/use-root-store";

const ProjectsPage = observer(() => {
  const { projectRoot } = useRootStore();
  const ids = projectRoot.workspaceProjectIds ?? [];

  return (
    <ul>
      {ids.map(id => (
        <li key={id}>{projectRoot.getProjectById(id)?.name}</li>
      ))}
    </ul>
  );
});

Global Styles and Assets

Global styling is implemented via Tailwind CSS and custom theme files in styles/*.css, imported by the root component. Static assets including icons, favicons, and the PWA manifest reside in public/.

The application includes Progressive Web App support through public/sw.js (service worker) and public/site.webmanifest.json, enabling optional offline usage for installed applications.

Development Workflow

Package Configuration

The package.json file defines the workspace dependencies and monorepo links to shared packages such as @plane/ui, @plane/types, and @plane/shared-state. Key scripts include:

  • dev – Starts the Vite development server
  • build – Creates the production client bundle
  • check:type – Runs TypeScript type checking

Environment Variables

Sensitive configuration is handled through environment variables prefixed with VITE_. Notably, when VITE_ENABLE_SESSION_RECORDER is set, the build injects Microsoft Clarity analytics scripts into the HTML head, filtered and validated in vite.config.ts.

Summary

  • The web directory in makeplane/plane is the React SPA frontend located at apps/web in the monorepo.
  • It uses Vite for building and React Router v7 with SSR disabled, producing a static client-only bundle.
  • MobX handles state management in core/store/, while core/services/ provides typed API clients for the Django backend.
  • The entry point app/root.tsx manages the HTML shell, theme provider, and global asset loading.
  • PWA support is built-in via service workers and web manifests in the public/ directory.

Frequently Asked Questions

What framework does the Plane web directory use?

The Plane web directory is built as a React single-page application using React Router v7 for navigation and Vite as the build tool. It leverages TypeScript throughout and uses Tailwind CSS for styling.

Is the Plane web app server-side rendered (SSR)?

No, SSR is explicitly disabled. The configuration in react-router.config.ts sets ssr: false, and vite.config.ts emits only client assets. This makes Plane a fully client-rendered application where the browser downloads a static JavaScript bundle that renders the UI dynamically.

How does the web directory communicate with the backend?

Communication occurs through the service layer in core/services/**. These TypeScript services use axios to make HTTP requests to the Django REST API, returning typed data transfer objects from @plane/types. MobX stores then consume these services to fetch and cache data.

What state management solution is used in Plane's web directory?

The application uses MobX for reactive state management. Stores in core/store/** hold observable state and actions, while components use mobx-react's observer function to reactively re-render when relevant state changes. This pattern separates UI components from data fetching logic.

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 →