slop audit
Blog post 14 / Release engineering

How to check broken links before launch

Learn how to check broken links before launch, test redirects and 404 responses, add a focused CI check, and verify the deployed page with clear evidence.

By Slop Audit · 8 min read

You should check broken links on the deployed website before launch, after redirects and hosting rules can affect the result. A link that works in local development can still point to an old route, a staging hostname, an empty fragment, or a page that returns an error in production.

This guide shows you how to find broken links, classify each failure, test the response your visitor receives, and keep a focused link check in CI. It also covers a related problem that many link checkers miss: an error page that looks like a 404 but returns HTTP 200.

Check broken links on the deployed URL

Start with the public URL that users and crawlers will open. Check the links in the rendered page and the links in the initial HTML because client-side code may add, remove, or replace navigation after load.

Build a small inventory for the release-critical page:

  • Internal navigation, breadcrumbs, footer links, and calls to action.
  • In-page fragments such as /docs#installation.
  • Downloads, email links, and external documentation.
  • Routes created from CMS entries or application data.

Keep the link text beside each destination. Google's link best practices recommend standard <a> elements with resolvable href values and descriptive anchor text. The destination and label form one contract: the URL must work, and the text must tell the visitor where it goes.

Run the check against representative pages instead of the homepage alone. Shared navigation may pass while a pricing card, documentation page, or old article still points to a deleted route. Add the result to your website launch checklist with the tested URL and release identifier.

Classify each failed link

A useful broken link report separates failures that need different owners:

  • HTTP error: the destination returns 404, 410, or a server error.
  • Bad redirect: the link reaches an unrelated page, loops, or crosses to the wrong host.
  • Placeholder destination: the visible link uses an empty href, #, or javascript:void(0).
  • Missing fragment: the page loads, but the target ID does not exist.
  • Runtime failure: a client-side router or script prevents the expected navigation.

Classify redirects before marking them broken. A permanent move may need a 301, while a temporary route may use a 302. Follow the chain and compare its final URL with the destination you intended. The canonical URL and redirects guide explains how internal links, redirects, canonicals, and sitemap entries should agree.

Placeholder anchors need a markup fix. MDN's `a` element reference explains that fake links such as href="#" and javascript:void(0) produce poor link behavior and incorrect semantics. Use a link for navigation to a real URL. Use a button when the control opens a dialog, changes a setting, or performs another in-page action.

Use a broken link checker in Playwright

You can turn the internal links on a critical page into a reviewable Playwright test. The example below loads the rendered document, resolves same-origin links, removes fragments, and checks each unique destination with an HTTP request:

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

test("homepage internal links return a usable response", async ({ page, request }) => {
  const startUrl = "https://example.com/";
  await page.goto(startUrl);

  const urls = await page.locator("a[href]").evaluateAll((links, pageUrl) => {
    const origin = new URL(pageUrl).origin;
    return [...new Set(links.flatMap((link) => {
      const href = link.getAttribute("href")?.trim();
      if (!href || href === "#" || href.startsWith("javascript:")) return [];
      const url = new URL(href, pageUrl);
      if (url.origin !== origin || !["http:", "https:"].includes(url.protocol)) return [];
      url.hash = "";
      return [url.toString()];
    }))];
  }, startUrl);

  const results = await Promise.all(urls.map(async (url) => {
    const response = await request.get(url, { failOnStatusCode: false });
    return { url, status: response.status(), finalUrl: response.url() };
  }));

  const failures = results.filter(({ status }) => status >= 400);
  expect(failures, JSON.stringify(failures, null, 2)).toEqual([]);
});

Playwright's API testing guide documents the request fixture used in this test. Keep the result's original URL, status, and final URL so the failure points to a specific route and redirect outcome.

Limit the test to your own origin first. External sites may rate-limit automation, block test infrastructure, or change without your release. Check business-critical external destinations in a separate job with a retry policy and an owner who can decide whether a vendor failure should block deployment.

Test fragments and client-side routes in the browser

An HTTP request cannot prove that an in-page fragment works. Load the source page, activate the link, and verify the target or destination in the rendered browser.

test("installation link reaches its section", async ({ page }) => {
  await page.goto("https://example.com/docs");
  await page.getByRole("link", { name: "Installation" }).click();
  await expect(page.locator("#installation")).toBeVisible();
  await expect(page).toHaveURL(/\/docs#installation$/);
});

Use the same approach for the primary call to action. Assert its accessible name, final route, and visible success state. A status-code link checker can miss a client router that leaves the visitor on the same page or a button styled as a link with no working action.

Check live links and the 404 response

A site needs valid live links and honest missing-page behavior. Request a safe path that you know does not exist, then confirm that the server returns 404 with a useful HTML page.

curl -sS -o /dev/null -w '%{http_code}\n' \
  https://example.com/release-check-path-that-does-not-exist

MDN defines HTTP 404 as the response for a resource the server cannot find. Google calls a missing page that returns 200 a soft 404 and recommends a real 404 or 410 when no replacement exists. Its crawling error guidance also recommends a custom error page that helps visitors return to useful content.

Single-page applications need extra attention. A catch-all hosting rule may serve the application shell with 200 for any path, even when the client renders “Page not found.” Configure the server or edge layer to send the correct status. If the platform cannot do that for a client-rendered route, follow Google's documented soft-404 guidance and keep the error page out of the index.

Fix the destination according to intent

Choose the fix from the content state:

  1. Correct a typo when the intended page still exists.
  2. Update the link and add a 301 when content moved to a clear replacement.
  3. Remove the link and return 404 or 410 when the content is gone without a replacement.
  4. Replace a fake anchor with a button when the action stays on the current page.

Avoid redirecting all missing URLs to the homepage. Visitors asked for a specific resource, and a generic destination hides the broken reference. Record the source page, broken destination, observed status, intended fix, and owner in the release task. The website audit report example shows how to keep evidence and the next action together.

Keep broken link checks in CI and monitoring

Run a small set of stable link checks on pull requests: the primary navigation, main call to action, current sitemap routes, and links changed by the branch. Run the broader public-URL check after deployment, where the CDN, redirect layer, and production content are present.

Save the tested source URL, destination, status, final URL, and release ID. That baseline lets your team distinguish a new failure from an old exception. The website regression testing guide covers baseline selection, incomplete measurements, and post-deployment monitoring.

Run Slop Audit to check broken links

Run a Slop Audit on the public page after deployment. Slop Audit checks a bounded sample of up to eight same-origin links from the initial HTML, flags visible empty or placeholder link destinations during Browser Run, checks the primary action's route when it qualifies for a safe probe, and requests a safe nonexistent path to test real 404 behavior. The report keeps the response evidence beside each finding so you can assign a concrete fix and rescan the page.

Slop Audit does not crawl a site's full link inventory or certify external-link coverage. Keep a full-site crawler for large inventories and retain end-to-end tests for application flows. Use the public-page audit as a focused release check for the page you plan to share.