System Architecture of React Playground: What Actually Happens Behind the Scenes
Source: Dev.to
High‑level Overview
- Frontend behaves like an IDE.
- Backend acts as a coordinator.
- Workers actually run the submitted code.
- Redis connects everything.
- S3 stores user solutions.
Frontend: Editor and Preview
Editing
- The Monaco Editor captures the JSX you type.
- Babel (standalone) transpiles JSX → JavaScript.
- The resulting JavaScript is injected into an iframe.
Why an iframe?
If the user’s code crashes, the iframe isolates the failure so the rest of the app stays alive. The iframe provides:
- A separate DOM
- A separate JavaScript context
React and ReactDOM are loaded via CDN inside the iframe, and the component is rendered there, giving instant feedback.
Submitting Code
When the user clicks “Submit”:
- The raw JSX (not the transpiled code) is sent to the backend.
- The backend creates a
solutionIdand returns it to the frontend.
At this point nothing has been executed yet.
Real‑time Communication
- The frontend opens a WebSocket connection and registers using the
solutionId. - The backend pushes results to the client via this socket (no polling).
Backend Coordination
Queueing Jobs
Instead of executing code directly in the main server, the backend:
- Pushes a job into a BullMQ queue.
- The job payload contains the code and relevant metadata.
Why a queue?
- Execution is slow, unpredictable, and potentially dangerous.
- Running it in the main server would block other requests and risk performance.
Worker Process
A dedicated worker continuously listens to the BullMQ queue. When a job arrives:
- The worker takes the job and transpiles the JSX again using Babel.
- It launches Puppeteer, injects the code into a real browser environment, and simulates user actions (e.g., finding a button, clicking it, checking UI updates).
This approach provides:
- A real DOM and event loop
- Browser‑level behavior that Node.js alone cannot emulate
Isolation and Safety
- Each execution runs in a new Puppeteer page with no shared state.
- The worker runs in a separate process from the backend, so failures stay contained.
Result Delivery
- After execution, the worker marks the job as completed in BullMQ.
- The backend listens for queue events, finds the correct WebSocket using
solutionId, and emits the result. - The frontend receives the result instantly and updates the UI.
Redis Usage
- Stores BullMQ queue data.
- Publishes job events (completed/failed).
- Acts as a caching layer for faster reads.
Switch from raw Pub/Sub to BullMQ simplified the architecture by providing built‑in lifecycle events, retry handling, and cleaner code.
Storing User Code
- Code is saved in AWS S3; only the file key is stored in the database.
- When needed, the backend generates a signed URL for the frontend to fetch the code.
Benefits
- Security (no public access)
- Scalability
- Clear separation between metadata and large code blobs
End‑to‑End Flow
- User writes code → preview runs in iframe.
- User submits → backend returns
solutionId. - Frontend opens WebSocket using the ID.
- Backend pushes job to BullMQ queue.
- Worker executes code via Puppeteer.
- Worker marks job completed; backend receives the event.
- Backend sends result through WebSocket.
- Frontend updates UI with validation outcome.
Key Advantages
- Frontend handles fast preview; backend handles reliable validation with a real DOM.
- Workers run independently, preventing blocking of the main server.
- Real‑time updates via WebSocket reduce load and improve scalability.
- Clear separation of concerns leads to a cleaner, more maintainable architecture.