supaguardsupaguardDocs
Use cases

How to Monitor a Next.js Application with Synthetic Monitoring

Set up production monitoring for your Next.js app. Learn to write Playwright tests for SSR pages, API Routes, middleware, and authentication with next-auth.

Next.js applications have unique monitoring needs. Server-side rendering (SSR), API Routes, middleware, and dynamic content all create potential failure points that traditional uptime monitors miss. Synthetic monitoring with Playwright catches these issues by simulating real user behavior.

Why Next.js Apps Need Synthetic Monitoring

Next.js combines frontend and backend in a single deployment, creating several surfaces that can break independently:

Next.js FeatureWhat Can BreakImpact
Server ComponentsRendering errors, data fetch failuresBlank pages or error boundaries
API RoutesHandler errors, database timeoutsBroken forms, failed actions
MiddlewareRedirect loops, auth gating failuresUsers locked out or seeing wrong content
ISR/StaticStale content, revalidation failuresOutdated information
Edge RuntimeCold start timeouts, region issuesSlow or unavailable pages

Monitoring Server-Rendered Pages

Next.js Server Components fetch data during rendering. If the data source fails, the entire page can break.

import { test, expect } from "@playwright/test";

test("homepage renders with dynamic content", async ({ page }) => {
  await page.goto("https://your-nextjs-app.com");

  // Verify SSR content loaded (not just the shell)
  await expect(page.getByRole("heading", { level: 1 })).toBeVisible();

  // Check that dynamic data rendered (not loading skeleton)
  await expect(page.getByTestId("featured-items")).not.toHaveText("Loading...");
  await expect(page.getByTestId("featured-items")).toBeVisible();
});

Monitoring API Routes

Test your Next.js API Routes directly to catch backend issues separately from frontend rendering:

test("API route returns valid data", async ({ request }) => {
  const response = await request.get(
    "https://your-nextjs-app.com/api/products"
  );

  expect(response.status()).toBe(200);

  const data = await response.json();
  expect(data.products).toBeDefined();
  expect(data.products.length).toBeGreaterThan(0);
});

Server Actions

If you use Next.js Server Actions, monitor the forms they power:

test("contact form submits successfully", async ({ page }) => {
  await page.goto("https://your-nextjs-app.com/contact");

  await page.getByLabel("Name").fill("Synthetic Test");
  await page.getByLabel("Email").fill("test@example.com");
  await page.getByLabel("Message").fill("Automated monitoring test");
  await page.getByRole("button", { name: "Send" }).click();

  // Server Action should redirect or show success
  await expect(page.getByText("Message sent")).toBeVisible();
});

Monitoring Authentication (next-auth)

Authentication is one of the most critical flows to monitor. If users can't log in, nothing else matters.

test("user can log in with credentials", async ({ page }) => {
  await page.goto("https://your-nextjs-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: "Sign In" }).click();

  // Verify redirect to authenticated page
  await expect(page).toHaveURL(/.*dashboard/);
  await expect(page.getByText("Welcome")).toBeVisible();
});

[!TIP] Create a dedicated test account for monitoring. Disable MFA, exclude from analytics, and give it minimal permissions. Store credentials in environment variables.

Monitoring Middleware and Redirects

Next.js middleware can redirect users, rewrite URLs, or modify headers. Monitor that these work correctly:

test("unauthenticated users are redirected to login", async ({ page }) => {
  // Visit a protected page without logging in
  const response = await page.goto(
    "https://your-nextjs-app.com/dashboard"
  );

  // Should redirect to login
  await expect(page).toHaveURL(/.*login/);
});

test("locale redirect works", async ({ page }) => {
  await page.goto("https://your-nextjs-app.com/", {
    extraHTTPHeaders: { "Accept-Language": "fr-FR" },
  });

  // Should redirect to French locale
  await expect(page).toHaveURL(/.*\/fr\/.*/);
});

Handling Dynamic and Client-Side Content

Next.js apps often use client-side data fetching for interactive sections:

test("dashboard loads real-time data", async ({ page }) => {
  // Login first
  await page.goto("https://your-nextjs-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: "Sign In" }).click();

  // Wait for client-side data to load
  await page.waitForLoadState("networkidle");

  // Verify dynamic content appeared
  await expect(page.getByTestId("stats-panel")).toBeVisible();
  await expect(page.getByTestId("recent-activity")).not.toHaveText("No data");
});
FlowFrequencyPriority
Homepage loadEvery 5 minHigh
Authentication (login)Every 5 minCritical
Core feature (dashboard)Every 10 minHigh
API Routes healthEvery 5 minHigh
Contact/signup formsEvery 15 minMedium
Marketing pagesEvery 30 minLow

Next Steps

On this page