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 serverbuild– Creates the production client bundlecheck: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
webdirectory inmakeplane/planeis the React SPA frontend located atapps/webin 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/, whilecore/services/provides typed API clients for the Django backend. - The entry point
app/root.tsxmanages 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →