How We Built Deployxa's Dashboard
Deployxa's dashboard is the primary interface for developers using the platform. It shows your apps, their deployment status, build logs (streaming in real time), metrics (CPU, memory, network), health checks, and environment variables. Building a real-time dashboard that handles thousands of concurrent users and streams live data is a significant engineering challenge, and we learned a lot of lessons along the way. Here is how we built it, the architecture decisions we made, and the lessons we learned.
The direct answer is that Deployxa's dashboard is a React app (built with Next.js) that runs on Deployxa's own persistent containers. The dashboard uses WebSockets for real-time updates (build logs, metrics, deployment status), REST APIs for CRUD operations (creating apps, setting environment variables), and Server-Sent Events (SSE) for one-way streaming (log tails). The dashboard is served from the same infrastructure as your apps, which means it benefits from the same Traefik v3 routing, automatic SSL, and blue/green deployments. For more on the infrastructure, see our article on Traefik v3 dynamic routing.
The Architecture
The dashboard has four main components:
1. The frontend (React + Next.js)
The frontend is a Next.js app that renders the dashboard UI. It uses React for component rendering, Tailwind CSS for styling, and SWR for data fetching. The frontend communicates with the backend via REST APIs (for CRUD operations) and WebSockets (for real-time updates).
2. The backend (Node.js + Express)
The backend is a Node.js app (built with Express) that serves the REST APIs and manages the WebSocket connections. The backend communicates with the Deployxa engine (which manages the containers) via internal gRPC calls.
3. The realtime layer (WebSockets + SSE)
The realtime layer handles streaming data: build logs (via WebSockets), metrics (via WebSockets), and deployment status (via SSE). The realtime layer uses a pub/sub model, where the backend publishes events to Redis, and the WebSocket servers subscribe to the events and forward them to the connected clients.
4. The data layer (Postgres + Redis)
The data layer stores persistent data (apps, deployments, environment variables) in Postgres and ephemeral data (log streams, metrics) in Redis. Postgres is the source of truth, and Redis is used for caching and pub/sub.
Step-by-Step: How the Dashboard Works
Here is how the dashboard works, step by step.
Step 1: The user opens the dashboard
When the user opens the dashboard, the frontend fetches the initial data (list of apps, current deployment status) via REST APIs. The frontend renders the UI with this data.
Step 2: The frontend establishes a WebSocket connection
For real-time updates (build logs, metrics, deployment status), the frontend establishes a WebSocket connection to the backend. The WebSocket connection is authenticated via the user's session token.
Step 3: The backend subscribes to events
When the WebSocket connection is established, the backend subscribes to the relevant Redis channels (e.g., logs:{app_id}, metrics:{app_id}, status:{app_id}). When an event is published to one of these channels, the backend forwards it to the connected client via the WebSocket.
Step 4: The Deployxa engine publishes events
When a build is running, the Deployxa engine publishes log lines to the logs:{app_id} Redis channel. When metrics are collected, the engine publishes metrics to the metrics:{app_id} channel. When a deployment's status changes, the engine publishes a status event to the status:{app_id} channel.
Step 5: The frontend renders the events
When the frontend receives an event via the WebSocket, it updates the UI. For log lines, the frontend appends them to the log viewer. For metrics, the frontend updates the charts. For status changes, the frontend updates the status badge.
Step 6: The user interacts with the dashboard
When the user interacts with the dashboard (e.g., triggers a deployment, sets an environment variable), the frontend sends a request to the backend via a REST API. The backend processes the request, updates the database, and (if relevant) publishes an event to Redis, which triggers a real-time UI update.
Common Pitfalls and Troubleshooting
The first pitfall is WebSocket scaling. A single WebSocket server can handle thousands of connections, but for tens of thousands of connections, you need multiple WebSocket servers and a pub/sub system (like Redis) to distribute events. The fix is to use a Redis-backed pub/sub layer, which lets multiple WebSocket servers share events. The second pitfall is authentication. WebSocket connections need to be authenticated, but the standard HTTP authentication (cookies, headers) does not work directly with WebSockets. The fix is to pass the authentication token as a query parameter during the WebSocket handshake, and to validate it on the server. For more on authentication, see our article on the JWT authentication trap. The third pitfall is reconnection. WebSocket connections can drop (due to network issues, server restarts, etc.), and the frontend needs to reconnect automatically. The fix is to use a reconnection library (e.g., reconnecting-websocket) that handles reconnection with exponential backoff. The fourth pitfall is backpressure. If the backend publishes events faster than the frontend can process them, the events queue up, which can cause memory issues. The fix is to implement flow control (e.g., the frontend tells the backend to slow down) or to drop events that are too old. The fifth pitfall is testing. Real-time apps are hard to test, because the events are asynchronous and the timing is non-deterministic. The fix is to use a testing library that supports async assertions (e.g., @testing-library/react with waitFor) and to mock the WebSocket connection in tests.
Performance: Handling Thousands of Concurrent Users
Handling thousands of concurrent users requires careful attention to performance. The first optimization is connection pooling. Each WebSocket connection consumes memory (for the connection state) and file descriptors (for the socket), which means a single server can handle a limited number of connections. The fix is to use a connection pool (e.g., via a load balancer that distributes connections across multiple servers). The second optimization is event batching. Instead of sending each event individually, the backend can batch events and send them in a single message, which reduces the overhead per event. The third optimization is compression. WebSocket messages can be compressed (via the permessage-deflate extension), which reduces the bandwidth usage. The fourth optimization is selective subscriptions. Instead of subscribing to all events for all apps, the frontend subscribes only to events for the app the user is currently viewing, which reduces the number of events the backend needs to forward. The fifth optimization is caching. The frontend caches the initial data (list of apps, current deployment status) and updates it via WebSocket events, which reduces the number of REST API calls. For more on performance, see our article on SPA vs SSR hardware sizing.
How the Dashboard Uses the MCP Server
The dashboard and the MCP server share the same underlying API, which means they have the same capabilities. The dashboard is the human interface, and the MCP server is the AI interface. For example, the dashboard's "Deploy" button and the MCP server's deployxa_deploy_workflow tool both call the same backend API; they just present the results differently. This means you can switch between the dashboard and the MCP server based on what you are doing: use the dashboard for visual inspection, use the MCP server for agentic workflows. For more on the MCP server, see our article on giving Cursor cloud superpowers.
Lessons Learned
Building the dashboard taught us several lessons. First, real-time is hard. WebSockets, pub/sub, reconnection, and backpressure are all complex topics that require careful engineering. Second, the user experience matters. A dashboard that is technically impressive but slow or confusing will frustrate users. We spent a lot of time optimizing the perceived performance (showing loading states, streaming logs incrementally) to make the dashboard feel fast. Third, testing is essential. Real-time apps are hard to test, but without tests, regressions are inevitable. We invested in a comprehensive test suite that mocks the WebSocket connection and verifies the UI updates correctly. Fourth, observability is critical. A real-time app has many moving parts (frontend, backend, WebSocket servers, Redis, Postgres), and without good observability, debugging issues is a nightmare. We use OpenTelemetry to trace requests across the entire stack, which lets us identify bottlenecks and diagnose issues quickly. For more on observability, see our article on debugging from your IDE with deployxa doctor.
Advanced Dashboard Patterns
Beyond the basics, real-time dashboards benefit from several advanced patterns. The first is optimistic updates. When the user takes an action (e.g., triggers a deployment), the dashboard can optimistically update the UI (e.g., show the deployment as "in progress") before the backend confirms the action. This makes the dashboard feel faster. The second is conflict resolution. If multiple users are viewing the same app and one takes an action (e.g., rolls back), the other users' dashboards need to update in real time. The fix is to use a pub/sub model (via Redis) that broadcasts changes to all connected clients. The third is offline support. If the user loses connectivity, the dashboard should show a friendly message and queue any actions for when connectivity returns. The fix is to use a service worker (via Workbox) that caches the dashboard's assets and queues actions. The fourth is accessibility. The dashboard should be usable by developers with disabilities (e.g., keyboard navigation, screen reader support). The fix is to use semantic HTML, ARIA attributes, and keyboard shortcuts. The fifth is internationalization. The dashboard should support multiple languages, because Deployxa's users are global. The fix is to use a translation library (e.g., i18next) and to maintain translations for common languages. For more on dashboard patterns, see our articles on the auto-detection engine and the heuristic advisor.
When a Custom Dashboard Is Not Needed
Building a custom dashboard is not always needed. For small teams, the Deployxa dashboard (which is already built) is sufficient. For teams that want a custom view (e.g., a dashboard that combines Deployxa data with data from other sources), building a custom dashboard is valuable. For teams that want to embed Deployxa data in an existing dashboard (e.g., Grafana), the Deployxa API (via the MCP server) provides the data. The key is to match the dashboard to the team's needs: for most teams, the Deployxa dashboard is sufficient; for teams with specific needs, a custom dashboard is valuable. For more on dashboards, see our articles on how we handle SSL at scale and the cost optimization engine.
Conclusion: A Real-Time Dashboard on Persistent Containers
Deployxa's dashboard is a real-time React app that handles thousands of concurrent users and streams live data. Building it required careful attention to architecture (WebSockets, pub/sub, caching), performance (connection pooling, event batching, compression), and user experience (perceived performance, loading states). The dashboard is served from the same infrastructure as your apps, which means it benefits from the same Traefik v3 routing, automatic SSL, and blue/green deployments. For more on Deployxa's engineering, see our articles on the auto-detection engine and the heuristic advisor. Learn about how we handle SSL at scale and the cost optimization engine in our companion articles. Explore our free developer tools to speed up your workflow. Try Deployxa Drop for an instant live preview with zero signup.