# Add URL Normalization Middleware in React Router

Used: react-router@8.0.0

Small URL differences can create duplicate pages. `/about` and `/about/` are different URLs, and so are `https://www.example.com/about` and `https://example.com/about`.

You can fix this at the edge of your React Router app with middleware. The goal is simple: inspect the incoming request URL, build the canonical version, and redirect before loaders and actions run.

## Create the Normalizer

Start with one function that receives a `URL` and returns the canonical URL. Keeping this logic outside the middleware makes it easy to test without a router.

```ts {% path="app/utils/normalize-url.ts" %}
export function normalizeUrl(url: URL) {
	let normalizedUrl = new URL(url);

	if (normalizedUrl.hostname.startsWith("www.")) {
		normalizedUrl.hostname = normalizedUrl.hostname.slice(4);
	}

	if (normalizedUrl.pathname !== "/" && normalizedUrl.pathname.endsWith("/")) {
		normalizedUrl.pathname = normalizedUrl.pathname.replace(/\/+$/, "");
	}

	return normalizedUrl;
}
```

This function removes the `www.` prefix and strips trailing slashes from non-root paths. It leaves `/` alone because the root path is already canonical.

## Add the Middleware

Now wrap the normalizer in React Router middleware. If the canonical URL differs from the request URL, redirect with HTTP 308 (Permanent Redirect).

```ts {% path="app/middleware/normalize-url.ts" %}
import type { MiddlewareFunction } from "react-router";
import { redirectDocument } from "react-router";

import { normalizeUrl } from "~/utils/normalize-url";

export let normalizeUrlMiddleware: MiddlewareFunction<Response> = ({ request }, next) => {
	let url = new URL(request.url);
	let normalizedUrl = normalizeUrl(url);

	if (normalizedUrl.href !== url.href) {
		throw redirectDocument(normalizedUrl.href, 308);
	}

	return next();
};
```

Using one middleware avoids redirect chains. A request for `https://www.example.com/about/` redirects directly to `https://example.com/about`.

HTTP 308 (Permanent Redirect) keeps the original request method. That is safer than HTTP 301 (Moved Permanently) if a non-GET request reaches a non-canonical URL.

## Register It at the Root

Export the middleware from your root route so it runs before every child route.

```tsx {% path="app/root.tsx" %}
import { normalizeUrlMiddleware } from "~/middleware/normalize-url";

export let middleware = [normalizeUrlMiddleware];

// ... rest of your root route
```

Root middleware is the right place for URL normalization because the rule is about the whole site, not one page. If a nested route adds its own middleware, React Router will still run the root middleware first.

## Invert the Rules

Some sites prefer `www.` hostnames or trailing slashes. Keep the same middleware shape and change only the utility rules.

```ts {% path="app/utils/normalize-url.ts" %}
export function normalizeUrl(url: URL) {
	let normalizedUrl = new URL(url);

	if (!normalizedUrl.hostname.startsWith("www.")) {
		normalizedUrl.hostname = `www.${normalizedUrl.hostname}`;
	}

	if (!normalizedUrl.pathname.endsWith("/")) {
		normalizedUrl.pathname = `${normalizedUrl.pathname}/`;
	}

	return normalizedUrl;
}
```

With this version, `https://example.com/about` redirects to `https://www.example.com/about/`. Pick one canonical shape and enforce it consistently.

## Verify the Redirects

Check the URLs that used to create duplicates:

- `https://www.example.com/about/` should redirect to `https://example.com/about`
- `https://example.com/about/` should redirect to `https://example.com/about`
- `https://www.example.com/` should redirect to `https://example.com/`

This middleware handles the redirect. For SEO, you should still render canonical links in your document head so crawlers and social previews see the same canonical URL in the HTML.