How to Monitor a React Application with Synthetic Monitoring
Set up production monitoring for your React app. Write Playwright tests for SPAs with client-side routing, lazy loading, state management, and loading states.
React single-page applications (SPAs) present unique monitoring challenges. Client-side routing, lazy-loaded components, and asynchronous state management mean traditional uptime checks miss most of the failure modes that affect real users. Synthetic monitoring with Playwright simulates actual user behavior to catch these issues.
Why React Apps Need Synthetic Monitoring
React SPAs differ fundamentally from server-rendered pages:
| Challenge | Why It Matters |
|---|---|
| Client-side routing | URL changes don't trigger page loads — uptime monitors see nothing |
| Lazy loading | Components load on demand — failures only appear when users navigate |
| JavaScript errors | A thrown error can break the entire app (white screen of death) |
| API dependencies | UI depends on API responses — backend failures show as frontend bugs |
| Loading states | Spinners and skeletons can persist forever if data fetch fails |
Monitoring Core User Flows
Navigation and Routing
React Router (or Tanstack Router, etc.) handles routing client-side. Verify navigation works:
import { test, expect } from "@playwright/test";
test("navigation between pages works", async ({ page }) => {
await page.goto("https://your-react-app.com");
// Navigate via client-side routing
await page.getByRole("link", { name: "Products" }).click();
await expect(page).toHaveURL(/.*products/);
await expect(page.getByRole("heading", { name: "Products" })).toBeVisible();
// Navigate to another page
await page.getByRole("link", { name: "About" }).click();
await expect(page).toHaveURL(/.*about/);
await expect(page.getByRole("heading", { name: "About" })).toBeVisible();
});Authentication Flow
test("user can log in and access dashboard", async ({ page }) => {
await page.goto("https://your-react-app.com/login");
await page.getByLabel("Email").fill(process.env.TEST_USER_EMAIL!);
await page.getByLabel("Password").fill(process.env.TEST_USER_PASSWORD!);
await page.getByRole("button", { name: "Log In" }).click();
// Wait for client-side redirect and auth state to settle
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.getByTestId("user-menu")).toBeVisible();
});Form Submission
test("form submits and shows confirmation", async ({ page }) => {
await page.goto("https://your-react-app.com/contact");
await page.getByLabel("Name").fill("Test User");
await page.getByLabel("Email").fill("test@example.com");
await page.getByLabel("Message").fill("Monitoring test message");
await page.getByRole("button", { name: "Send" }).click();
// Wait for success state (not loading)
await expect(page.getByText("Message sent successfully")).toBeVisible();
});Handling React-Specific Patterns
Loading States and Suspense
React apps use loading states, skeletons, and Suspense boundaries. Wait for them to resolve:
test("dashboard data loads completely", async ({ page }) => {
await page.goto("https://your-react-app.com/dashboard");
// Wait for loading state to finish
await expect(page.getByTestId("loading-skeleton")).not.toBeVisible({
timeout: 15000,
});
// Verify actual data rendered
await expect(page.getByTestId("revenue-card")).toBeVisible();
await expect(page.getByTestId("revenue-card")).not.toHaveText("$0");
});Error Boundaries
React error boundaries catch rendering errors. Monitor that they don't appear:
test("no error boundaries triggered", async ({ page }) => {
await page.goto("https://your-react-app.com/dashboard");
// Verify error boundary is NOT showing
await expect(page.getByText("Something went wrong")).not.toBeVisible();
await expect(page.getByText("Error")).not.toBeVisible();
// Verify actual content is showing
await expect(page.getByTestId("main-content")).toBeVisible();
});Modal and Overlay Interactions
React modals often use portals. Playwright handles them naturally:
test("settings modal opens and saves", async ({ page }) => {
await page.goto("https://your-react-app.com/dashboard");
await page.getByRole("button", { name: "Settings" }).click();
// Modal content (rendered via portal)
await expect(page.getByRole("dialog")).toBeVisible();
await page.getByLabel("Display Name").fill("Updated Name");
await page.getByRole("button", { name: "Save" }).click();
// Modal should close and changes should persist
await expect(page.getByRole("dialog")).not.toBeVisible();
await expect(page.getByText("Updated Name")).toBeVisible();
});Blocking Third-Party Scripts
React apps often include analytics, chat widgets, and other third-party scripts that can interfere with tests:
test("core flow without third-party interference", async ({ page }) => {
// Block scripts that might interfere
await page.route("**/analytics.js", (route) => route.abort());
await page.route("**/chat-widget/**", (route) => route.abort());
await page.route("**/hotjar.com/**", (route) => route.abort());
await page.goto("https://your-react-app.com");
// ... rest of test
});Monitoring API Dependencies
React apps depend heavily on APIs. Monitor them alongside the UI:
test("API and UI are in sync", async ({ page }) => {
await page.goto("https://your-react-app.com/products");
// Wait for API response and verify UI renders it
const responsePromise = page.waitForResponse("**/api/products");
await page.getByRole("button", { name: "Load Products" }).click();
const response = await responsePromise;
expect(response.status()).toBe(200);
// Verify UI updated with API data
await expect(page.getByRole("listitem")).toHaveCount(10);
});Recommended Check Configuration
| Flow | Frequency | Priority |
|---|---|---|
| Login flow | Every 5 min | Critical |
| Core feature (main page) | Every 5 min | Critical |
| Navigation between routes | Every 10 min | High |
| Form submission | Every 15 min | Medium |
| Settings and profile | Every 30 min | Low |
Next Steps
- Modern SPA Monitoring — Deep dive into SPA patterns
- Writing Playwright Tests — Playwright fundamentals
- Environment Variables — Secure credential storage
- Troubleshooting — Fix common monitoring issues
How to Monitor Password Reset Flow (Complete SaaS Guide)
Learn how to monitor a password reset flow with synthetic checks, from forgot-password requests to token validation and successful login completion.
How to Monitor SaaS Billing Flows and Prevent Revenue Incidents
Set up synthetic billing monitors for checkout, upgrades, payment retries, and customer portal access to protect SaaS revenue paths.