How the React Router Application Works in Plane’s Frontend: File-Based Routes, SSR, and MobX State Management

Plane’s React Router application leverages React Router v7’s file-based routing in apps/web/app/routes, server-side rendering via @react-router/node, and a MobX RouterStore to manage reactive URL state across the project management interface.

Plane’s open-source project management platform implements a modern React Router architecture in its apps/web package to deliver a fast, SEO-friendly user experience. The application uses the React Router v7 ecosystem—including @react-router/dev, @react-router/node, and @react-router/serve—combined with TypeScript and MobX for type-safe navigation and state management. This setup enables automatic route discovery, server-side rendering (SSR) for instant first paints, and reactive client-side navigation without full page reloads.

File-Based Route Configuration

Plane adopts file-based routing, where the filesystem structure directly defines the application’s URL hierarchy. The route manifest lives in apps/web/app/routes and is automatically scanned by the React Router build toolchain.

Route Definitions and Manifest Generation

Each route is a TypeScript file that exports route configuration objects, loaders, or actions. During the build process, @react-router/dev discovers these files and generates a route manifest in the .react-router folder. For example, authentication redirects are handled in dedicated route files:

These files are fed into the router at runtime, creating a type-safe mapping between URLs and components.

Router Configuration and Build Setup

The root configuration for Plane’s React Router application resides in apps/web/react-router.config.ts. This configuration file defines the route file pattern and build options for both client and server bundles.

import type { Config } from "@react-router/dev/config";

export const config: Config = {
  routes: "app/routes/**/*.{tsx,ts}",
  // Additional options for asset handling and entry points
};

The Vite configuration in apps/web/vite.config.ts consumes this config through the @react-router/dev plugin. This integration handles:

  • Development: Hot module replacement and fast refresh via react-router dev
  • Production: Generation of client bundles (.react-dir/client) and server bundles (.react-router/server) for SSR

Server-Side Rendering Architecture

Plane’s React Router implementation supports full server-side rendering to improve initial load performance and search engine visibility.

Development vs. Production Rendering

During development, the command pnpm dev executes react-router dev --port 3000, which runs a specialized dev server using @react-router/node. This server renders routes on-demand for fast iteration. In production, the build process (react-router build) generates a static server bundle at build/server/index.js. The production server is launched using react-router serve, as configured in deployments/aio/community/supervisor.conf, delivering pre-rendered HTML that hydrates into a dynamic React application on the client.

Query Parameter Management with MobX

Plane synchronizes URL query parameters with application state through a dedicated MobX store called RouterStore. Located in apps/web/core/store/router.store.ts, this store parses the URL query string and exposes computed properties for common route parameters.

export class RouterStore implements IRouterStore {
  @observable query: ParsedUrlQuery = {};

  @action.bound setQuery(query: ParsedUrlQuery) {
    this.query = query;
  }

  @computed get workspaceSlug() {
    return this.query?.workspaceSlug?.toString();
  }

  @computed get projectId() {
    return this.query?.projectId?.toString();
  }

  @computed get issueId() {
    return this.query?.issueId?.toString();
  }
}

The framework calls RouterStore.setQuery() whenever the URL query string changes. Components observing routerStore.workspaceSlug or routerStore.projectId react automatically to navigation events without manually parsing window.location.

Plane provides utility functions for constructing navigation URLs. The generateQueryParams function in packages/utils/src/router.ts creates URL-encoded query strings while allowing specific keys to be excluded—useful when building filtered navigation links.

export const generateQueryParams = (
  searchParams: URLSearchParams,
  excludedParamKeys?: string[]
) => {
  const params = new URLSearchParams(searchParams);
  excludedParamKeys?.forEach((key) => params.delete(key));
  return params.toString();
};

This helper ensures consistent URL formatting across the application when programmatically generating navigation links or redirect URLs.

Component-Level Router Integration

Components throughout Plane’s frontend import routing primitives directly from react-router to handle navigation, parameter extraction, and location awareness.

Essential Hooks and Components

  • useParams – Extracts dynamic route parameters such as projectId from URLs like /projects/:projectId
  • useNavigate – Enables programmatic navigation after form submissions or user actions
  • Link – Provides declarative navigation with prefetching capabilities
  • useLocation – Accesses the current location object for breadcrumb generation or conditional UI rendering

Practical Implementation Example

The following pattern appears in navigation components like apps/web/core/components/navigation/tab-navigation-root.tsx:

import { Link, useNavigate, useParams, useLocation } from "react-router";

export const ProjectHeader = () => {
  const { projectId } = useParams();
  const navigate = useNavigate();
  const location = useLocation();

  const goToSettings = () => navigate(`/projects/${projectId}/settings`);

  return (
    <header>
      <Link to={`/projects/${projectId}`}>Back to Project</Link>
      <button onClick={goToSettings}>Settings</button>
      <span>Current path: {location.pathname}</span>
    </header>
  );
};

Similarly, modal components like apps/web/core/components/pages/modals/export-page-modal.tsx use useParams to determine context-specific data without prop drilling.

Runtime Navigation Flow

Understanding the React Router application’s runtime behavior reveals how Plane balances server performance with client interactivity:

  1. Initial Server Render – When a user requests a URL, the server bundle (build/server/index.js) executes the matching route’s loader, renders the component tree, and streams HTML to the browser.
  2. Client Hydration – React Router’s client bundle takes over the static markup, attaching event listeners and initializing the RouterStore with the current URL state.
  3. Client-Side Navigation – Clicking a <Link> intercepts the default browser behavior, updates the URL via the History API, fetches any necessary loader data, and re-renders only the changed route components.
  4. State Synchronization – Query parameter changes trigger RouterStore.setQuery(), causing MobX observers to recalculate computed properties and update dependent UI elements reactively.

Summary

  • File-based routing in apps/web/app/routes automatically generates the route manifest through React Router v7’s build tools.
  • SSR configuration in apps/web/react-router.config.ts and vite.config.ts enables both development hot-reloading and production server-side rendering via @react-router/node.
  • MobX integration through apps/web/core/store/router.store.ts provides reactive, computed access to URL parameters like workspaceSlug and projectId.
  • Utility functions such as generateQueryParams in packages/utils/src/router.ts standardize query string manipulation across the codebase.
  • Component hooks including useParams, useNavigate, and Link from react-router enable type-safe, declarative navigation patterns throughout the application.

Frequently Asked Questions

How does Plane handle server-side rendering with React Router?

Plane uses @react-router/node to render routes on the server during both development and production. In development, react-router dev provides a fast SSR dev server, while production builds generate a static server bundle at build/server/index.js that is served by react-router serve. This approach delivers pre-rendered HTML for SEO and performance, which then hydrates into a fully interactive client-side application.

What is the purpose of the RouterStore in Plane’s frontend?

The RouterStore in apps/web/core/store/router.store.ts is a MobX store that parses the URL query string into reactive state. It exposes computed properties like workspaceSlug, projectId, and issueId that components can observe. When the URL changes, the framework calls setQuery(), automatically triggering updates in any component consuming these computed values without requiring manual URL parsing.

How are routes defined in Plane’s React Router application?

Routes are defined using file-based routing in the apps/web/app/routes directory. Each .tsx or .ts file exports route configurations, loaders, or actions that React Router v7 discovers automatically based on the pattern defined in react-router.config.ts. For example, apps/web/app/routes/redirects/core/signin.tsx handles authentication redirects using the redirect helper from React Router.

What build tools does Plane use for its React Router implementation?

Plane uses Vite with the @react-router/dev plugin to handle the build process. The configuration in apps/web/vite.config.ts works alongside apps/web/react-router.config.ts to generate client bundles for the browser and server bundles for SSR. The build outputs are stored in .react-router/ during development and build/ for production deployments.

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 →