Next.js 15 Middleware: 301 Redirects Without SEO Loss — KEL IT
Websites 5 min read

Next.js 15 Middleware: Clean Redirects for SEO Landings

Your landing goes live on Vercel. A week later Search Console shows dozens of “Page with redirect” URLs, duplicate /pricing and /pricing/ entries, and old backlinks hitting 404. You set canonical tags, built a sitemap, added JSON-LD — yet crawlers still waste budget on junk URL variants and dilute signals to the canonical page.

The issue isn’t content or Core Web Vitals. Redirects are half-configured at the CDN, and the App Router doesn’t know your legacy paths. next.config redirects help but don’t cover dynamic cases: geo prefixes, campaign slugs, or query normalization.

Middleware in Next.js 15 runs on the edge before any page render. It’s the right layer for SEO infrastructure on a landing site: one source of truth for 301/308, trailing slashes, www → apex, and post-redesign URL migrations.

Why Redirects Belong in Your SEO Stack

Google treats each URL as a document identity. Two URLs with identical content compete for indexing. Canonical hints help, but crawlers still fetch both variants, external links may point at the wrong one, and without 301s a slug change doesn’t transfer link equity.

ScenarioWithout middlewareWith edge 301
/about/ vs /aboutDuplicate candidatesSingle canonical
Old /blog/post after migration404, traffic loss301 → /articles/post
www.example.comApex duplicate301 → example.com

For small service landings crawl budget rarely hurts. For programmatic SEO (city or vertical pages at scale), every extra URL counts. Middleware scales: one function, sub-5 ms on Vercel Edge.

Use 301 (or 308 when POST safety matters) for permanent moves. Reserve 302/307 for temporary campaigns.

middleware.ts Basics

Place middleware.ts at the project root. Restrict the matcher so you don’t run logic on /_next assets or static files.

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const url = request.nextUrl.clone();
  const { pathname } = url;
  const host = request.headers.get('host') ?? '';

  if (host.startsWith('www.')) {
    url.host = host.replace(/^www\./, '');
    return NextResponse.redirect(url, 301);
  }

  if (pathname !== '/' && pathname.endsWith('/')) {
    url.pathname = pathname.slice(0, -1);
    return NextResponse.redirect(url, 301);
  }

  return NextResponse.next();
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)',
  ],
};

Clone nextUrl before mutating. Keep middleware, canonical URLs, sitemap entries, and <Link href> in the same format — mismatches trigger Search Console warnings.

If you need this kind of implementation — message on Telegram.

Legacy URL Migration

After a redesign, Google and backlinks still reference old paths. A static map in middleware beats bloating next.config when you have dozens of rules:

const LEGACY: Record<string, string> = {
  '/services/web-development': '/services/websites',
  '/portfolio': '/cases',
};

export function middleware(request: NextRequest) {
  const url = request.nextUrl.clone();
  const target = LEGACY[url.pathname];
  if (target) {
    url.pathname = target;
    return NextResponse.redirect(url, 301);
  }
  // trailing slash, www, etc.
}

For pattern-based old URLs (/blog/2024/slug/articles/slug), apply regex after exact matches. Preserve query strings (UTMs) unless you explicitly clear url.search.

Marketing teams sometimes need /promo-winter/pricing?ref=winter without a deploy. Vercel Edge Config can store a redirect map; use 302 for short promos and 301 in code for permanent slug changes.

Pitfalls and Verification

Avoid redirect chains (301 → 301 → 301), loops, redirecting / elsewhere, and heavy fetch on every request — TTFB and LCP suffer. Aim for one hop from any legacy URL to the final canonical.

Verify with:

curl -IL https://www.example.com/Pricing/
# Should end with a single 200 at https://example.com/pricing

In Search Console, “Page with redirect” is expected for old URLs; it’s a problem if your canonical URL lives there. Submit a sitemap of final URLs only.

Pick either middleware or next.config redirects for the same paths — not both — to prevent double hops.

Need help? Telegram → or vic.kell@ya.ru

FAQ

Middleware vs next.config redirects?

Config redirects are declarative and require rebuilds. Middleware supports conditions, regex, and Edge Config — better for SEO migrations and URL normalization.

Trailing slash: with or without?

Either works for Google; consistency matters. Match middleware, canonical, sitemap, and internal links. Next.js defaults to no trailing slash.

Does middleware hurt Core Web Vitals?

A lean edge function doesn’t move LCP or INP. Heavy per-request logic on static assets does — tighten the matcher.

Redirect differently for Googlebot?

No — that’s cloaking. Same 301 for users and bots.

Conclusion

A Next.js 15 landing isn’t SEO-complete with metadata and CWV alone. URL hygiene via edge middleware prevents duplicates, preserves equity on migrations, and quiets Search Console noise. One middleware.ts with a legacy map and slash rules — aligned with canonical and sitemap — deploys globally on Vercel in milliseconds.

Before restructuring, export old URLs from Analytics and Search Console, map 301s, validate chains with curl -IL, and refresh the sitemap. Your hero images and JSON-LD won’t matter if half your backlinks still land on 404.

KEL IT

Need a custom solution?

I build these types of projects professionally. Telegram bots, Mini Apps, websites, mobile and desktop applications. Tell me about your project and I'll get back to you with a plan.